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 @@

Crates.io Docs & Demos - 35 Functions + 38 Functions


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 @@

Crates.io Docs & Demos - 35 Functions + 38 Functions

\ 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