finished new functions and release

This commit is contained in:
Maccesch 2023-06-24 01:12:43 +01:00
parent fadfcab1c1
commit 4999313f32
15 changed files with 116 additions and 242 deletions

View file

@ -1,3 +1,3 @@
[build]
[unstable]
rustflags = ["--cfg=web_sys_unstable_apis"]
rustdocflags = ["--cfg=web_sys_unstable_apis"]

View file

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

View file

@ -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"]
rustc-args = ["--cfg=web_sys_unstable_apis"]
[dev-dependencies]
leptos = "0.3"

View file

@ -12,7 +12,7 @@
<p align="center">
<a href="https://crates.io/crates/leptos-use"><img src="https://img.shields.io/crates/v/leptos-use.svg?label=&color=%232C1275" alt="Crates.io"/></a>
<a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-docs%20%26%20demos-%239A233F" alt="Docs & Demos"></a>
<a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-35%20functions-%23EF3939" alt="35 Functions" /></a>
<a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-38%20functions-%23EF3939" alt="38 Functions" /></a>
</p>
<br/>

View file

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

View file

@ -11,6 +11,6 @@
<p>
<a href="https://crates.io/crates/leptos-use"><img src="https://img.shields.io/crates/v/leptos-use.svg?label=&color=%232C1275" alt="Crates.io"/></a>
<a href="./get_started.html"><img src="https://img.shields.io/badge/-docs%20%26%20demos-%239A233F" alt="Docs & Demos"></a>
<a href="./functions.html"><img src="https://img.shields.io/badge/-35%20functions-%23EF3939" alt="35 Functions" /></a>
<a href="./functions.html"><img src="https://img.shields.io/badge/-38%20functions-%23EF3939" alt="38 Functions" /></a>
</p>
</div>

View file

@ -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,
<button on:click=move |_| next()>
{ move || format!("{}", state.get()) }
</button>
<Note>"Click to change the color mode"</Note>
}
}

View file

@ -264,8 +264,8 @@ select {
--tw-backdrop-sepia: ;
}
.block {
display: block;
.static {
position: static;
}
.text-\[--brand-color\] {

View file

@ -1,2 +0,0 @@
[toolchain]
channel = "stable"

View file

@ -1,12 +1,11 @@
use leptos::*;
use std::pin::Pin;
pub enum MaybeRwSignal<T>
where
T: 'static,
{
Static(T),
DynamicRw(ReadSignal<T>, WriteSignal<T>),
DynamicRw(Signal<T>, WriteSignal<T>),
DynamicRead(Signal<T>),
}
@ -14,8 +13,8 @@ impl<T: Clone> Clone for MaybeRwSignal<T> {
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<T> From<Memo<T>> for MaybeRwSignal<T> {
impl<T> From<RwSignal<T>> for MaybeRwSignal<T> {
fn from(s: RwSignal<T>) -> Self {
let (r, w) = s.split();
Self::DynamicRw(r, w)
Self::DynamicRw(r.into(), w)
}
}
impl<T> From<(ReadSignal<T>, WriteSignal<T>)> for MaybeRwSignal<T> {
fn from(s: (ReadSignal<T>, WriteSignal<T>)) -> Self {
Self::DynamicRw(s.0.into(), s.1)
}
}
impl<T> From<(Signal<T>, WriteSignal<T>)> for MaybeRwSignal<T> {
fn from(s: (Signal<T>, WriteSignal<T>)) -> Self {
Self::DynamicRw(s.0, s.1)
}
}
@ -71,187 +76,18 @@ impl From<&str> for MaybeRwSignal<String> {
}
}
impl<T: Clone> SignalGet<T> for MaybeRwSignal<T> {
fn get(&self) -> T {
impl<T: Clone> MaybeRwSignal<T> {
pub fn to_signal(&self, cx: Scope) -> (Signal<T>, WriteSignal<T>) {
match self {
Self::Static(t) => t.clone(),
Self::DynamicRw(r, _) => r.get(),
Self::DynamicRead(s) => s.get(),
}
}
fn try_get(&self) -> Option<T> {
match self {
Self::Static(t) => Some(t.clone()),
Self::DynamicRw(r, _) => r.try_get(),
Self::DynamicRead(s) => s.try_get(),
}
}
}
impl<T> SignalWith<T> for MaybeRwSignal<T> {
fn with<O>(&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<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
match self {
Self::Static(t) => Some(f(t)),
Self::DynamicRw(r, _) => r.try_with(f),
Self::DynamicRead(s) => s.try_with(f),
}
}
}
impl<T> SignalWithUntracked<T> for MaybeRwSignal<T> {
fn with_untracked<O>(&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<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
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<T: Clone> SignalGetUntracked<T> for MaybeRwSignal<T> {
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<T> {
match self {
Self::Static(t) => Some(t.clone()),
Self::DynamicRw(r, _) => r.try_get_untracked(),
Self::DynamicRead(s) => s.try_get_untracked(),
}
}
}
impl<T: Clone> SignalStream<T> for MaybeRwSignal<T> {
fn to_stream(&self, cx: Scope) -> Pin<Box<dyn futures::stream::Stream<Item = T>>> {
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<T> MaybeRwSignal<T> {
pub fn derive(cx: Scope, derived_signal: impl Fn() -> T + 'static) -> Self {
Self::DynamicRead(Signal::derive(cx, derived_signal))
}
}
impl<T> SignalSetUntracked<T> for MaybeRwSignal<T> {
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<T> {
match self {
Self::DynamicRw(_, w) => w.try_set_untracked(new_value),
_ => Some(new_value),
}
}
}
impl<T> SignalUpdateUntracked<T> for MaybeRwSignal<T> {
#[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<O>(&self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
match self {
Self::DynamicRw(_, w) => w.try_update_untracked(f),
_ => Some(f()),
}
}
}
impl<T> SignalUpdate<T> for MaybeRwSignal<T> {
#[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<O>(&self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
match self {
Self::DynamicRw(_, w) => w.try_update(f),
_ => Some(f()),
}
}
}
impl<T> SignalSet<T> for MaybeRwSignal<T> {
#[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<T> {
match self {
Self::DynamicRw(_, w) => w.try_set(new_value),
_ => Some(new_value),
}
}
}
impl<T> SignalDispose for MaybeRwSignal<T> {
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)
}
}
}

View file

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

View file

@ -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<ColorMode>,
storage_signal: Option<RwSignal<ColorMode>>,
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<ColorMode>, WriteSignal<ColorMode>) {
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<String> for ColorMode {
fn from(s: String) -> Self {
ColorMode::from(s.as_str())
}
}
/// Arguments to [`UseColorModeOptions::on_changed`]
#[derive(Clone)]
pub struct UseColorModeOnChangeArgs {

View file

@ -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<MaybeSignal<T>>,
initial_value: Option<MaybeRwSignal<T>>,
/// The default index when the current value is not found in the list.
/// For example when `get_index_of` returns `None`.

View file

@ -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<MaybeSignal<String>>) -> Sign
let media_query: Rc<RefCell<Option<web_sys::MediaQueryList>>> = Rc::new(RefCell::new(None));
let remove_listener: RemoveListener = Rc::new(RefCell::new(None));
let listener: Rc<OnceCell<Box<dyn CloneableFnMutWithArg<web_sys::Event>>>> =
Rc::new(OnceCell::new());
let listener: Rc<RefCell<Box<dyn CloneableFnMutWithArg<web_sys::Event>>>> =
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<MaybeSignal<String>>) -> 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<MaybeSignal<String>>) -> Sign
{
let update = update.clone();
listener
.set(Box::new(move |_| update()) as Box<dyn CloneableFnMutWithArg<web_sys::Event>>)
.expect("cell is empty");
.replace(Box::new(move |_| update()) as Box<dyn CloneableFnMutWithArg<web_sys::Event>>);
}
create_effect(cx, move |_| update());

View file

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