diff --git a/demo_markdown/docs/calendar/mod.md b/demo_markdown/docs/calendar/mod.md index 968a7c8..611c7dc 100644 --- a/demo_markdown/docs/calendar/mod.md +++ b/demo_markdown/docs/calendar/mod.md @@ -2,10 +2,14 @@ ```rust demo use chrono::prelude::*; -let value = RwSignal::new(Some(Local::now().date_naive())); +let value = RwSignal::new(Local::now().date_naive()); +let option_value = RwSignal::new(Some(Local::now().date_naive())); view! { - + + + + } ``` diff --git a/demo_markdown/docs/date_picker/mod.md b/demo_markdown/docs/date_picker/mod.md index d3f4783..a537d93 100644 --- a/demo_markdown/docs/date_picker/mod.md +++ b/demo_markdown/docs/date_picker/mod.md @@ -2,10 +2,14 @@ ```rust demo use chrono::prelude::*; -let value = RwSignal::new(Some(Local::now().date_naive())); +let value = RwSignal::new(Local::now().date_naive()); +let option_value = RwSignal::new(Some(Local::now().date_naive())); view! { - + + + + } ``` diff --git a/demo_markdown/docs/radio/mod.md b/demo_markdown/docs/radio/mod.md index d3b6889..3b0e098 100644 --- a/demo_markdown/docs/radio/mod.md +++ b/demo_markdown/docs/radio/mod.md @@ -1,15 +1,25 @@ # Radio ```rust demo -let value = RwSignal::new(None); +let value = RwSignal::new(String::new()); +let option_value = RwSignal::new(None); view! { - - - - + + + + + + + + + +
- "value: " {move || format!("{:?}", value.get())} + "value: " {move || format!("{}", value.get())} +
+
+ "option_value: " {move || format!("{:?}", option_value.get())}
} ``` diff --git a/demo_markdown/docs/time_picker/mod.md b/demo_markdown/docs/time_picker/mod.md index 7355e28..fc36196 100644 --- a/demo_markdown/docs/time_picker/mod.md +++ b/demo_markdown/docs/time_picker/mod.md @@ -3,10 +3,14 @@ ```rust demo use chrono::prelude::*; -let value = RwSignal::new(Some(Local::now().time())); +let value = RwSignal::new(Local::now().time()); +let option_value = RwSignal::new(Local::now().time()); view! { - + + + + } ``` diff --git a/thaw/src/calendar/mod.rs b/thaw/src/calendar/mod.rs index 8c17b6f..81e9698 100644 --- a/thaw/src/calendar/mod.rs +++ b/thaw/src/calendar/mod.rs @@ -2,12 +2,12 @@ use crate::{Button, ButtonGroup}; use chrono::{Datelike, Days, Local, Month, Months, NaiveDate}; use leptos::prelude::*; use std::ops::Deref; -use thaw_utils::{class_list, mount_style, Model, OptionalProp}; +use thaw_utils::{class_list, mount_style, OptionModel, OptionalProp}; #[component] pub fn Calendar( #[prop(optional, into)] class: OptionalProp>, - #[prop(optional, into)] value: Model>, + #[prop(optional, into)] value: OptionModel, ) -> impl IntoView { mount_style("calendar", include_str!("./calendar.css")); let show_date = RwSignal::new(value.get_untracked().unwrap_or(now_date())); @@ -138,13 +138,13 @@ pub fn Calendar( #[component] fn CalendarItem( - value: Model>, + value: OptionModel, index: usize, date: CalendarItemDate, ) -> impl IntoView { let is_selected = Memo::new({ let date = date.clone(); - move |_| value.with(|value_date| value_date.as_ref() == Some(date.deref())) + move |_| value.with(|value_date| value_date == Some(date.deref())) }); let weekday_str = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; let on_click = { diff --git a/thaw/src/date_picker/mod.rs b/thaw/src/date_picker/mod.rs index fcc281f..7af016d 100644 --- a/thaw/src/date_picker/mod.rs +++ b/thaw/src/date_picker/mod.rs @@ -4,11 +4,11 @@ use chrono::NaiveDate; use leptos::{html, prelude::*}; use panel::{Panel, PanelRef}; use thaw_components::{Binder, Follower, FollowerPlacement}; -use thaw_utils::{mount_style, now_date, ComponentRef, Model, OptionalProp}; +use thaw_utils::{mount_style, now_date, ComponentRef, OptionModel, OptionalProp}; #[component] pub fn DatePicker( - #[prop(optional, into)] value: Model>, + #[prop(optional, into)] value: OptionModel, #[prop(optional, into)] class: OptionalProp>, ) -> impl IntoView { mount_style("date-picker", include_str!("./date-picker.css")); @@ -18,7 +18,7 @@ pub fn DatePicker( let show_date_format = "%Y-%m-%d"; let update_show_date_text = move || { value.with_untracked(move |date| { - let text = date.as_ref().map_or(String::new(), |date| { + let text = date.map_or(String::new(), |date| { date.format(show_date_format).to_string() }); show_date_text.set(text); @@ -28,7 +28,7 @@ pub fn DatePicker( let panel_ref = ComponentRef::::default(); let panel_selected_date = RwSignal::new(None::); _ = panel_selected_date.watch(move |date| { - let text = date.as_ref().map_or(String::new(), |date| { + let text = date.map_or(String::new(), |date| { date.format(show_date_format).to_string() }); show_date_text.set(text); diff --git a/thaw/src/radio/mod.rs b/thaw/src/radio/mod.rs index fb1b565..5fe3de1 100644 --- a/thaw/src/radio/mod.rs +++ b/thaw/src/radio/mod.rs @@ -15,17 +15,14 @@ pub fn Radio( mount_style("radio", include_str!("./radio.css")); let id = uuid::Uuid::new_v4().to_string(); - let group = RadioGroupInjection::use_(); + let group = RadioGroupInjection::expect_context(); let item_value = StoredValue::new(value); let checked = Memo::new({ let group = group.clone(); move |_| { - item_value.with_value(|value| { - group - .value - .with(|group_value| group_value.as_ref() == Some(value)) - }) + item_value + .with_value(|value| group.value.with(|group_value| group_value == Some(value))) } }); diff --git a/thaw/src/radio/radio_group.rs b/thaw/src/radio/radio_group.rs index a6af90a..2c46941 100644 --- a/thaw/src/radio/radio_group.rs +++ b/thaw/src/radio/radio_group.rs @@ -1,9 +1,9 @@ use leptos::{context::Provider, prelude::*}; -use thaw_utils::Model; +use thaw_utils::OptionModel; #[component] pub fn RadioGroup( - #[prop(optional, into)] value: Model>, + #[prop(optional, into)] value: OptionModel, /// The name of this radio group. #[prop(optional, into)] name: Option, @@ -22,12 +22,12 @@ pub fn RadioGroup( #[derive(Clone)] pub(crate) struct RadioGroupInjection { - pub value: Model>, + pub value: OptionModel, pub name: String, } impl RadioGroupInjection { - pub fn use_() -> RadioGroupInjection { + pub fn expect_context() -> Self { expect_context() } } diff --git a/thaw/src/time_picker/mod.rs b/thaw/src/time_picker/mod.rs index 8c89b1c..3a64c78 100644 --- a/thaw/src/time_picker/mod.rs +++ b/thaw/src/time_picker/mod.rs @@ -5,11 +5,11 @@ use crate::{ use chrono::{Local, NaiveTime, Timelike}; use leptos::{html, prelude::*}; use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement}; -use thaw_utils::{mount_style, ComponentRef, Model, OptionalProp}; +use thaw_utils::{mount_style, ComponentRef, OptionModel, OptionalProp}; #[component] pub fn TimePicker( - #[prop(optional, into)] value: Model>, + #[prop(optional, into)] value: OptionModel, #[prop(optional, into)] class: OptionalProp>, // #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, ) -> impl IntoView { diff --git a/thaw_utils/src/lib.rs b/thaw_utils/src/lib.rs index 7e79cc3..e55b712 100644 --- a/thaw_utils/src/lib.rs +++ b/thaw_utils/src/lib.rs @@ -16,7 +16,7 @@ pub use event_listener::{add_event_listener, add_event_listener_with_bool, Event pub use hooks::{use_click_position, use_lock_html_scroll, NextFrame}; pub use on_click_outside::*; pub use optional_prop::OptionalProp; -pub use signals::{ComponentRef, Model, OptionalMaybeSignal, SignalWatch, StoredMaybeSignal}; +pub use signals::*; pub use throttle::throttle; pub use time::now_date; diff --git a/thaw_utils/src/signals/mod.rs b/thaw_utils/src/signals/mod.rs index a491aee..3290905 100644 --- a/thaw_utils/src/signals/mod.rs +++ b/thaw_utils/src/signals/mod.rs @@ -5,7 +5,7 @@ mod signal_watch; mod stored_maybe_signal; pub use component_ref::ComponentRef; -pub use model::Model; +pub use model::{Model, OptionModel}; pub use optional_maybe_signal::OptionalMaybeSignal; pub use signal_watch::SignalWatch; pub use stored_maybe_signal::StoredMaybeSignal; diff --git a/thaw_utils/src/signals/model.rs b/thaw_utils/src/signals/model.rs index 6d072c3..b286f7b 100644 --- a/thaw_utils/src/signals/model.rs +++ b/thaw_utils/src/signals/model.rs @@ -1,3 +1,7 @@ +mod option_model; + +pub use option_model::OptionModel; + use leptos::reactive_graph::{ computed::Memo, signal::{ReadSignal, RwSignal, WriteSignal}, diff --git a/thaw_utils/src/signals/model/option_model.rs b/thaw_utils/src/signals/model/option_model.rs new file mode 100644 index 0000000..618d41a --- /dev/null +++ b/thaw_utils/src/signals/model/option_model.rs @@ -0,0 +1,171 @@ +use leptos::reactive_graph::{ + computed::Memo, + signal::{ReadSignal, RwSignal, WriteSignal}, + traits::{Get, GetUntracked, Set, With, WithUntracked}, + wrappers::read::Signal, +}; + +pub enum OptionModel +where + T: 'static, +{ + T(Signal, WriteSignal, Option>), + Option( + Signal>, + WriteSignal>, + Option>>, + ), +} + +impl Default for OptionModel { + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl Clone for OptionModel { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for OptionModel {} + +impl OptionModel { + fn new(value: T) -> Self { + let rw_signal = RwSignal::new(Some(value)); + rw_signal.into() + } + + fn new_option(value: Option) -> Self { + let rw_signal = RwSignal::new(value); + rw_signal.into() + } + + pub fn with(&self, fun: impl FnOnce(Option<&T>) -> O) -> O { + match self { + Self::T(read, _, _) => read.with(|value| fun(Some(value))), + Self::Option(read, _, _) => read.with(|value| fun(value.as_ref())), + } + } + + pub fn with_untracked(&self, fun: impl FnOnce(Option<&T>) -> O) -> O { + match self { + Self::T(read, _, _) => read.with_untracked(|value| fun(Some(value))), + Self::Option(read, _, _) => read.with_untracked(|value| fun(value.as_ref())), + } + } +} + +impl OptionModel { + pub fn get(&self) -> Option { + match self { + Self::T(read, _, _) => Some(read.get()), + Self::Option(read, _, _) => read.get(), + } + } + + pub fn get_untracked(&self) -> Option { + match self { + Self::T(read, _, _) => Some(read.get_untracked()), + Self::Option(read, _, _) => read.get_untracked(), + } + } +} + +impl OptionModel { + pub fn set(&self, value: Option) { + match self { + Self::T(read, write, on_write) => { + write.set(value.unwrap_or_default()); + if let Some(on_write) = on_write.as_ref() { + on_write.set(read.get_untracked()); + } + } + Self::Option(read, write, on_write) => { + write.set(value); + if let Some(on_write) = on_write.as_ref() { + on_write.set(read.get_untracked()); + } + } + } + } +} + +impl From for OptionModel { + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl From> for OptionModel { + fn from(rw_signal: RwSignal) -> Self { + let (read, write) = rw_signal.split(); + Self::T(read.into(), write, None) + } +} + +impl From>> for OptionModel { + fn from(rw_signal: RwSignal>) -> Self { + let (read, write) = rw_signal.split(); + Self::Option(read.into(), write, None) + } +} + +impl From<(Signal, WriteSignal)> for OptionModel { + fn from((read, write): (Signal, WriteSignal)) -> Self { + Self::T(read, write, None) + } +} + +impl From<(Signal>, WriteSignal>)> for OptionModel { + fn from((read, write): (Signal>, WriteSignal>)) -> Self { + Self::Option(read, write, None) + } +} + +impl From<(ReadSignal, WriteSignal)> for OptionModel { + fn from((read, write): (ReadSignal, WriteSignal)) -> Self { + Self::T(read.into(), write, None) + } +} + +impl From<(ReadSignal>, WriteSignal>)> for OptionModel { + fn from((read, write): (ReadSignal>, WriteSignal>)) -> Self { + Self::Option(read.into(), write, None) + } +} + +impl From<(Memo, WriteSignal)> for OptionModel { + fn from((read, write): (Memo, WriteSignal)) -> Self { + Self::T(read.into(), write, None) + } +} + +impl From<(Memo>, WriteSignal>)> for OptionModel { + fn from((read, write): (Memo>, WriteSignal>)) -> Self { + Self::Option(read.into(), write, None) + } +} + +impl From<(Option, WriteSignal)> for OptionModel { + fn from((read, write): (Option, WriteSignal)) -> Self { + let mut model = Self::new(read.unwrap_or_default()); + if let OptionModel::T(_, _, on_write) = &mut model { + *on_write = Some(write); + } + model + } +} + +impl From<(Option>, WriteSignal>)> + for OptionModel +{ + fn from((read, write): (Option>, WriteSignal>)) -> Self { + let mut model = Self::new_option(read.unwrap_or_default()); + if let OptionModel::Option(_, _, on_write) = &mut model { + *on_write = Some(write); + } + model + } +}