diff --git a/.cargo/config.toml b/.cargo/config.toml
index ae09f70..b891103 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,3 +1,3 @@
-[build]
+[unstable]
rustflags = ["--cfg=web_sys_unstable_apis"]
rustdocflags = ["--cfg=web_sys_unstable_apis"]
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8b93d7b..dbbbebd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,19 @@
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).
+## [0.3.3] - 2023-06-24
+
+### New Functions 🚀
+
+- `use_color_mode`
+- `use_cycle_list`
+- `use_active_element`
+
+### Changes 🔥
+
+- You can now use this crate with the `stable` toolchain (thanks @lpotthast)
+- Set leptos dependency to `default-features = false` in order to enable SSR.
+
## [0.3.2] - 2023-06-17
### New Functions 🚀
diff --git a/Cargo.toml b/Cargo.toml
index d6c667d..4f1afb2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "leptos-use"
-version = "0.3.2"
+version = "0.3.3"
edition = "2021"
authors = ["Marc-Stefan Cassola"]
categories = ["gui", "web-programming"]
@@ -16,7 +16,7 @@ homepage = "https://leptos-use.rs"
leptos = { version = "0.3", default-features = false }
wasm-bindgen = "0.2"
js-sys = "0.3"
-default-struct-builder = { path = "../default-struct-builder" }
+default-struct-builder = "0.3"
num = { version = "0.4", optional = true }
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }
@@ -64,7 +64,10 @@ math = ["num"]
storage = ["serde", "serde_json", "web-sys/StorageEvent"]
stable = ["leptos/stable"]
-[package.metadata."docs.rs"]
+[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg=web_sys_unstable_apis"]
-rustc-args = ["--cfg=web_sys_unstable_apis"]
\ No newline at end of file
+rustc-args = ["--cfg=web_sys_unstable_apis"]
+
+[dev-dependencies]
+leptos = "0.3"
\ No newline at end of file
diff --git a/README.md b/README.md
index 304bfae..92fc5bd 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/book/src/extract_doc_comment.py b/docs/book/src/extract_doc_comment.py
index 762b32f..966bfd0 100644
--- a/docs/book/src/extract_doc_comment.py
+++ b/docs/book/src/extract_doc_comment.py
@@ -64,6 +64,9 @@ def main():
print(line)
+ elif re.match(r"^//\s?#\[doc\(cfg\(.+?\)\)]", stripped_line):
+ pass
+
elif doc_comment_started:
initial_doc_finished = True
diff --git a/docs/book/src/introduction.md b/docs/book/src/introduction.md
index 09ee0b6..529d56c 100644
--- a/docs/book/src/introduction.md
+++ b/docs/book/src/introduction.md
@@ -11,6 +11,6 @@
-
+
\ No newline at end of file
diff --git a/examples/use_color_mode/src/main.rs b/examples/use_color_mode/src/main.rs
index a51b0c8..48439ac 100644
--- a/examples/use_color_mode/src/main.rs
+++ b/examples/use_color_mode/src/main.rs
@@ -1,17 +1,26 @@
-use leptos::html::Col;
+use leptos::html::html;
use leptos::*;
-use leptos_use::docs::demo_or_body;
+use leptos_use::docs::{demo_or_body, Note};
use leptos_use::{
- use_color_mode_with_options, use_cycle_list, ColorMode, UseColorModeOptions,
- UseColorModeReturn, UseCycleListReturn,
+ use_color_mode_with_options, use_cycle_list_with_options, ColorMode, UseColorModeOptions,
+ UseColorModeReturn, UseCycleListOptions, UseCycleListReturn,
};
#[component]
fn Demo(cx: Scope) -> impl IntoView {
- let UseColorModeReturn { mode, set_mode, .. } =
- use_color_mode_with_options(cx, UseColorModeOptions::default());
+ let UseColorModeReturn { mode, set_mode, .. } = use_color_mode_with_options(
+ cx,
+ UseColorModeOptions::default()
+ .custom_modes(vec![
+ "rust".into(),
+ "coal".into(),
+ "navy".into(),
+ "ayu".into(),
+ ])
+ .initial_value(ColorMode::from(html(cx).class_name())),
+ );
- let UseCycleListReturn { state, next, .. } = use_cycle_list(
+ let UseCycleListReturn { state, next, .. } = use_cycle_list_with_options(
cx,
vec![
ColorMode::Light,
@@ -20,9 +29,14 @@ fn Demo(cx: Scope) -> impl IntoView {
ColorMode::Custom("navy".into()),
ColorMode::Custom("ayu".into()),
],
+ UseCycleListOptions::default().initial_value(Some((mode, set_mode).into())),
);
view! { cx,
+
+ "Click to change the color mode"
}
}
diff --git a/examples/use_color_mode/style/output.css b/examples/use_color_mode/style/output.css
index ab5191f..2a1a99e 100644
--- a/examples/use_color_mode/style/output.css
+++ b/examples/use_color_mode/style/output.css
@@ -264,8 +264,8 @@ select {
--tw-backdrop-sepia: ;
}
-.block {
- display: block;
+.static {
+ position: static;
}
.text-\[--brand-color\] {
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
deleted file mode 100644
index 292fe49..0000000
--- a/rust-toolchain.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-[toolchain]
-channel = "stable"
diff --git a/src/core/maybe_rw_signal.rs b/src/core/maybe_rw_signal.rs
index 877c0cb..54aa9f9 100644
--- a/src/core/maybe_rw_signal.rs
+++ b/src/core/maybe_rw_signal.rs
@@ -1,12 +1,11 @@
use leptos::*;
-use std::pin::Pin;
pub enum MaybeRwSignal
where
T: 'static,
{
Static(T),
- DynamicRw(ReadSignal, WriteSignal),
+ DynamicRw(Signal, WriteSignal),
DynamicRead(Signal),
}
@@ -14,8 +13,8 @@ impl Clone for MaybeRwSignal {
fn clone(&self) -> Self {
match self {
Self::Static(t) => Self::Static(t.clone()),
- Self::DynamicRw(r, w) => Self::DynamicRw(r, w),
- Self::DynamicRead(s) => Self::DynamicRead(s),
+ Self::DynamicRw(r, w) => Self::DynamicRw(*r, *w),
+ Self::DynamicRead(s) => Self::DynamicRead(*s),
}
}
}
@@ -55,12 +54,18 @@ impl From> for MaybeRwSignal {
impl From> for MaybeRwSignal {
fn from(s: RwSignal) -> Self {
let (r, w) = s.split();
- Self::DynamicRw(r, w)
+ Self::DynamicRw(r.into(), w)
}
}
impl From<(ReadSignal, WriteSignal)> for MaybeRwSignal {
fn from(s: (ReadSignal, WriteSignal)) -> Self {
+ Self::DynamicRw(s.0.into(), s.1)
+ }
+}
+
+impl From<(Signal, WriteSignal)> for MaybeRwSignal {
+ fn from(s: (Signal, WriteSignal)) -> Self {
Self::DynamicRw(s.0, s.1)
}
}
@@ -71,187 +76,18 @@ impl From<&str> for MaybeRwSignal {
}
}
-impl SignalGet for MaybeRwSignal {
- fn get(&self) -> T {
+impl MaybeRwSignal {
+ pub fn to_signal(&self, cx: Scope) -> (Signal, WriteSignal) {
match self {
- Self::Static(t) => t.clone(),
- Self::DynamicRw(r, _) => r.get(),
- Self::DynamicRead(s) => s.get(),
- }
- }
-
- fn try_get(&self) -> Option {
- match self {
- Self::Static(t) => Some(t.clone()),
- Self::DynamicRw(r, _) => r.try_get(),
- Self::DynamicRead(s) => s.try_get(),
- }
- }
-}
-
-impl SignalWith for MaybeRwSignal {
- fn with(&self, f: impl FnOnce(&T) -> O) -> O {
- match self {
- Self::Static(t) => f(t),
- Self::DynamicRw(r, w) => r.with(f),
- Self::DynamicRead(s) => s.with(f),
- }
- }
-
- fn try_with(&self, f: impl FnOnce(&T) -> O) -> Option {
- match self {
- Self::Static(t) => Some(f(t)),
- Self::DynamicRw(r, _) => r.try_with(f),
- Self::DynamicRead(s) => s.try_with(f),
- }
- }
-}
-
-impl SignalWithUntracked for MaybeRwSignal {
- fn with_untracked(&self, f: impl FnOnce(&T) -> O) -> O {
- match self {
- Self::Static(t) => f(t),
- Self::DynamicRw(r, _) => r.with_untracked(f),
- Self::DynamicRead(s) => s.with_untracked(f),
- }
- }
-
- fn try_with_untracked(&self, f: impl FnOnce(&T) -> O) -> Option {
- match self {
- Self::Static(t) => Some(f(t)),
- Self::DynamicRw(r, _) => r.try_with_untracked(f),
- Self::DynamicRead(s) => s.try_with_untracked(f),
- }
- }
-}
-
-impl SignalGetUntracked for MaybeRwSignal {
- fn get_untracked(&self) -> T {
- match self {
- Self::Static(t) => t.clone(),
- Self::DynamicRw(r, _) => r.get_untracked(),
- Self::DynamicRead(s) => s.get_untracked(),
- }
- }
-
- fn try_get_untracked(&self) -> Option {
- match self {
- Self::Static(t) => Some(t.clone()),
- Self::DynamicRw(r, _) => r.try_get_untracked(),
- Self::DynamicRead(s) => s.try_get_untracked(),
- }
- }
-}
-
-impl SignalStream for MaybeRwSignal {
- fn to_stream(&self, cx: Scope) -> Pin>> {
- match self {
- Self::Static(t) => {
- let t = t.clone();
-
- let stream = futures::stream::once(async move { t });
-
- Box::bin(stream)
+ Self::DynamicRead(s) => {
+ // TODO : this feels hacky
+ let (_, w) = create_signal(cx, s.get_untracked());
+ (*s, w)
}
- Self::DynamicRw(r, _) => r.to_stream(cx),
- Self::DynamicRead(s) => s.to_stream(cx),
- }
- }
-}
-
-impl MaybeRwSignal {
- pub fn derive(cx: Scope, derived_signal: impl Fn() -> T + 'static) -> Self {
- Self::DynamicRead(Signal::derive(cx, derived_signal))
- }
-}
-
-impl SignalSetUntracked for MaybeRwSignal {
- fn set_untracked(&self, new_value: T) {
- match self {
- Self::DynamicRw(_, w) => w.set_untracked(new_value),
- _ => {
- // do nothing
- }
- }
- }
-
- fn try_set_untracked(&self, new_value: T) -> Option {
- match self {
- Self::DynamicRw(_, w) => w.try_set_untracked(new_value),
- _ => Some(new_value),
- }
- }
-}
-
-impl SignalUpdateUntracked for MaybeRwSignal {
- #[inline(always)]
- fn update_untracked(&self, f: impl FnOnce(&mut T)) {
- match self {
- Self::DynamicRw(_, w) => w.update_untracked(f),
- _ => {
- // do nothing
- }
- }
- }
-
- #[inline(always)]
- fn try_update_untracked(&self, f: impl FnOnce(&mut T) -> O) -> Option {
- match self {
- Self::DynamicRw(_, w) => w.try_update_untracked(f),
- _ => Some(f()),
- }
- }
-}
-
-impl SignalUpdate for MaybeRwSignal {
- #[inline(always)]
- fn update(&self, f: impl FnOnce(&mut T)) {
- match self {
- Self::DynamicRw(_, w) => w.update(f),
- _ => {
- // do nothing
- }
- }
- }
-
- #[inline(always)]
- fn try_update(&self, f: impl FnOnce(&mut T) -> O) -> Option {
- match self {
- Self::DynamicRw(_, w) => w.try_update(f),
- _ => Some(f()),
- }
- }
-}
-
-impl SignalSet for MaybeRwSignal {
- #[inline(always)]
- fn set(&self, new_value: T) {
- match self {
- Self::DynamicRw(_, w) => w.set(new_value),
- _ => {
- // do nothing
- }
- }
- }
-
- fn try_set(&self, new_value: T) -> Option {
- match self {
- Self::DynamicRw(_, w) => w.try_set(new_value),
- _ => Some(new_value),
- }
- }
-}
-
-impl SignalDispose for MaybeRwSignal {
- fn dispose(self) {
- match self {
- Self::DynamicRw(r, w) => {
- r.dispose();
- w.dispose();
- }
- Self::DynamicRead(s) => s.dispose(),
- _ => {
- // do nothing
+ Self::DynamicRw(r, w) => (*r, *w),
+ Self::Static(v) => {
+ let (r, w) = create_signal(cx, v.clone());
+ (Signal::from(r), w)
}
}
}
diff --git a/src/core/storage.rs b/src/core/storage.rs
index 95fe29d..72da8fa 100644
--- a/src/core/storage.rs
+++ b/src/core/storage.rs
@@ -2,7 +2,7 @@ use leptos::window;
use wasm_bindgen::JsValue;
/// Local or session storage or a custom store that is a `web_sys::Storage`.
-#[doc(cfg(feature = "storage"))]
+// #[doc(cfg(feature = "storage"))]
#[derive(Default)]
pub enum StorageType {
#[default]
diff --git a/src/use_color_mode.rs b/src/use_color_mode.rs
index 5049de6..e94a9c0 100644
--- a/src/use_color_mode.rs
+++ b/src/use_color_mode.rs
@@ -3,6 +3,7 @@ use crate::core::ElementMaybeSignal;
use crate::storage::{use_storage_with_options, UseStorageOptions};
#[cfg(feature = "storage")]
use serde::{Deserialize, Serialize};
+use std::fmt::{Display, Formatter};
use crate::core::StorageType;
use crate::use_preferred_dark;
@@ -54,11 +55,11 @@ use wasm_bindgen::JsCast;
/// # fn Demo(cx: Scope) -> impl IntoView {
/// # let UseColorModeReturn { mode, set_mode, .. } = use_color_mode(cx);
/// #
-/// mode(); // ColorMode::Dark or ColorMode::Light
+/// mode.get(); // ColorMode::Dark or ColorMode::Light
///
-/// set_mode(ColorMode::Dark); // change to dark mode and persist (with feature `storage`)
+/// set_mode.set(ColorMode::Dark); // change to dark mode and persist (with feature `storage`)
///
-/// set_mode(ColorMode::Auto); // change to auto mode
+/// set_mode.set(ColorMode::Auto); // change to auto mode
/// #
/// # view! { cx, }
/// # }
@@ -182,7 +183,7 @@ where
if attribute == "class" {
for mode in &modes {
- if value == ColorMode::from_string(mode) {
+ if &value.to_string() == mode {
let _ = el.class_list().add_1(mode);
} else {
let _ = el.class_list().remove_1(mode);
@@ -231,7 +232,10 @@ where
on_changed(state.get());
});
- let mode = Signal::derive(cx, move || if emit_auto { store.get() } else { state() });
+ let mode = Signal::derive(
+ cx,
+ move || if emit_auto { store.get() } else { state.get() },
+ );
UseColorModeReturn {
mode,
@@ -259,10 +263,10 @@ fn get_store_signal(
cx: Scope,
initial_value: MaybeSignal,
storage_signal: Option>,
- storage_key: &String,
- storage_enabled: bool,
- storage: StorageType,
- listen_to_storage_changes: bool,
+ _storage_key: &String,
+ _storage_enabled: bool,
+ _storage: StorageType,
+ _listen_to_storage_changes: bool,
) -> (ReadSignal, WriteSignal) {
if let Some(storage_signal) = storage_signal {
storage_signal.split()
@@ -273,7 +277,7 @@ fn get_store_signal(
#[cfg(feature = "storage")]
/// Color modes
-#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
pub enum ColorMode {
#[default]
Auto,
@@ -310,21 +314,21 @@ fn get_store_signal(
}
}
-impl ToString for ColorMode {
- fn to_string(&self) -> String {
+impl Display for ColorMode {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use ColorMode::*;
match self {
- Auto => "".to_string(),
- Light => "light".to_string(),
- Dark => "dark".to_string(),
- Custom(v) => v.clone(),
+ Auto => write!(f, "auto"),
+ Light => write!(f, "light"),
+ Dark => write!(f, "dark"),
+ Custom(v) => write!(f, "{}", v),
}
}
}
-impl ColorMode {
- pub fn from_string(s: &str) -> ColorMode {
+impl From<&str> for ColorMode {
+ fn from(s: &str) -> Self {
match s {
"auto" => ColorMode::Auto,
"" => ColorMode::Auto,
@@ -335,6 +339,12 @@ impl ColorMode {
}
}
+impl From for ColorMode {
+ fn from(s: String) -> Self {
+ ColorMode::from(s.as_str())
+ }
+}
+
/// Arguments to [`UseColorModeOptions::on_changed`]
#[derive(Clone)]
pub struct UseColorModeOnChangeArgs {
diff --git a/src/use_cycle_list.rs b/src/use_cycle_list.rs
index 4d770ee..a248b0b 100644
--- a/src/use_cycle_list.rs
+++ b/src/use_cycle_list.rs
@@ -1,3 +1,4 @@
+use crate::core::MaybeRwSignal;
use crate::watch;
use default_struct_builder::DefaultBuilder;
use leptos::*;
@@ -21,11 +22,11 @@ use leptos::*;
/// vec!["Dog", "Cat", "Lizard", "Shark", "Whale", "Dolphin", "Octopus", "Seal"]
/// );
///
-/// log!("{}", state()); // "Dog"
+/// log!("{}", state.get()); // "Dog"
///
/// prev();
///
-/// log!("{}", state()); // "Seal"
+/// log!("{}", state.get()); // "Seal"
/// #
/// # view! { cx, }
/// # }
@@ -77,14 +78,14 @@ where
move || {
if let Some(initial_value) = initial_value {
- initial_value.get()
+ initial_value
} else {
- first.expect("The provided list shouldn't be empty")
+ MaybeRwSignal::from(first.expect("The provided list shouldn't be empty"))
}
}
};
- let (state, set_state) = create_signal(cx, get_initial_value());
+ let (state, set_state) = get_initial_value().to_signal(cx);
let index = {
let list = list.clone();
@@ -162,7 +163,7 @@ where
};
UseCycleListReturn {
- state: state.into(),
+ state,
set_state,
index: index.into(),
set_index: set,
@@ -181,7 +182,7 @@ where
/// The initial value of the state. Can be a Signal. If none is provided the first entry
/// of the list will be used.
#[builder(keep_type)]
- initial_value: Option>,
+ initial_value: Option>,
/// The default index when the current value is not found in the list.
/// For example when `get_index_of` returns `None`.
diff --git a/src/use_media_query.rs b/src/use_media_query.rs
index 5c09e9d..137b87d 100644
--- a/src/use_media_query.rs
+++ b/src/use_media_query.rs
@@ -2,7 +2,7 @@ use crate::use_event_listener;
use crate::utils::CloneableFnMutWithArg;
use leptos::ev::change;
use leptos::*;
-use std::cell::{OnceCell, RefCell};
+use std::cell::RefCell;
use std::rc::Rc;
/// Reactive [Media Query](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Testing_media_queries).
@@ -40,8 +40,8 @@ pub fn use_media_query(cx: Scope, query: impl Into>) -> Sign
let media_query: Rc>> = Rc::new(RefCell::new(None));
let remove_listener: RemoveListener = Rc::new(RefCell::new(None));
- let listener: Rc>>> =
- Rc::new(OnceCell::new());
+ let listener: Rc>>> =
+ Rc::new(RefCell::new(Box::new(|_| {})));
let cleanup = {
let remove_listener = Rc::clone(&remove_listener);
@@ -70,10 +70,7 @@ pub fn use_media_query(cx: Scope, query: impl Into>) -> Sign
cx,
media_query.clone(),
change,
- listener
- .get()
- .expect("cell should be initialized by now")
- .clone(),
+ listener.borrow().clone(),
))));
} else {
set_matches.set(false);
@@ -84,8 +81,7 @@ pub fn use_media_query(cx: Scope, query: impl Into>) -> Sign
{
let update = update.clone();
listener
- .set(Box::new(move |_| update()) as Box>)
- .expect("cell is empty");
+ .replace(Box::new(move |_| update()) as Box>);
}
create_effect(cx, move |_| update());
diff --git a/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs b/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs
index cfbfc57..ef41a02 100644
--- a/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs
+++ b/template/src/{{ module }}/{{ function_name }}.ffizer.hbs.rs
@@ -20,19 +20,19 @@ use leptos::*;
/// # view! { cx, }
/// # }
/// ```{{#if feature}}
-#[doc(cfg(feature = "{{feature}}"))]{{/if}}
+// #[doc(cfg(feature = "{{feature}}"))]{{/if}}
pub fn {{ function_name }}({{#if scope }}cx: Scope{{/if}}) -> {{ to_pascal_case function_name }}Return {
{{ function_name }}_with_options({{#if scope }}cx, {{/if}}{{ to_pascal_case function_name }}Options::default())
}
/// Version of [`{{ function_name }}`] that takes a `{{ to_pascal_case function_name }}Options`. See [`{{ function_name }}`] for how to use.{{#if feature}}
-#[doc(cfg(feature = "{{feature}}"))]{{/if}}
+// #[doc(cfg(feature = "{{feature}}"))]{{/if}}
pub fn {{ function_name }}_with_options({{#if scope }}cx: Scope, {{/if}}options: {{ to_pascal_case function_name }}Options) -> {{ to_pascal_case function_name }}Return {
{{ to_pascal_case function_name }}Return {}
}
/// Options for [`{{ function_name }}_with_options`].{{#if feature}}
-#[doc(cfg(feature = "{{feature}}"))]{{/if}}
+// #[doc(cfg(feature = "{{feature}}"))]{{/if}}
#[derive(DefaultBuilder)]
pub struct {{ to_pascal_case function_name }}Options {}
@@ -42,6 +42,6 @@ impl Default for {{ to_pascal_case function_name }}Options {
}
}
-/// Return type of [`{{ function_name }}`].
-{{#if feature}}#[doc(cfg(feature = "{{feature}}"))]{{/if}}
+/// Return type of [`{{ function_name }}`].{{#if feature}}
+// #[doc(cfg(feature = "{{feature}}"))]{{/if}}
pub struct {{ to_pascal_case function_name }}Return {}
\ No newline at end of file