feat: adds OptionModel

This commit is contained in:
luoxiao 2024-07-24 23:52:38 +08:00
parent a7918ef2df
commit a653960040
13 changed files with 228 additions and 34 deletions

View file

@ -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! {
<Space vertical=true>
<Calendar value />
<Calendar value=option_value />
</Space>
}
```

View file

@ -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! {
<Space vertical=true>
<DatePicker value/>
<DatePicker value=option_value/>
</Space>
}
```

View file

@ -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! {
<Space vertical=true>
<RadioGroup value>
<Radio value="a" label="Apple"/>
<Radio value="o" label="Orange"/>
</RadioGroup>
<RadioGroup value=option_value>
<Radio value="a" label="Apple"/>
<Radio value="o" label="Orange"/>
</RadioGroup>
</Space>
<div style="margin-top: 1rem">
"value: " {move || format!("{:?}", value.get())}
"value: " {move || format!("{}", value.get())}
</div>
<div style="margin-top: 1rem">
"option_value: " {move || format!("{:?}", option_value.get())}
</div>
}
```

View file

@ -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! {
<Space vertical=true>
<TimePicker value />
<TimePicker value=option_value />
</Space>
}
```

View file

@ -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<MaybeSignal<String>>,
#[prop(optional, into)] value: Model<Option<NaiveDate>>,
#[prop(optional, into)] value: OptionModel<NaiveDate>,
) -> 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<Option<NaiveDate>>,
value: OptionModel<NaiveDate>,
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 = {

View file

@ -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<Option<NaiveDate>>,
#[prop(optional, into)] value: OptionModel<NaiveDate>,
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
) -> 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::<PanelRef>::default();
let panel_selected_date = RwSignal::new(None::<NaiveDate>);
_ = 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);

View file

@ -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)))
}
});

View file

@ -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<Option<String>>,
#[prop(optional, into)] value: OptionModel<String>,
/// The name of this radio group.
#[prop(optional, into)]
name: Option<String>,
@ -22,12 +22,12 @@ pub fn RadioGroup(
#[derive(Clone)]
pub(crate) struct RadioGroupInjection {
pub value: Model<Option<String>>,
pub value: OptionModel<String>,
pub name: String,
}
impl RadioGroupInjection {
pub fn use_() -> RadioGroupInjection {
pub fn expect_context() -> Self {
expect_context()
}
}

View file

@ -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<Option<NaiveTime>>,
#[prop(optional, into)] value: OptionModel<NaiveTime>,
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
// #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
) -> impl IntoView {

View file

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

View file

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

View file

@ -1,3 +1,7 @@
mod option_model;
pub use option_model::OptionModel;
use leptos::reactive_graph::{
computed::Memo,
signal::{ReadSignal, RwSignal, WriteSignal},

View file

@ -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<T>
where
T: 'static,
{
T(Signal<T>, WriteSignal<T>, Option<WriteSignal<T>>),
Option(
Signal<Option<T>>,
WriteSignal<Option<T>>,
Option<WriteSignal<Option<T>>>,
),
}
impl<T: Default + Send + Sync> Default for OptionModel<T> {
fn default() -> Self {
Self::new(Default::default())
}
}
impl<T> Clone for OptionModel<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for OptionModel<T> {}
impl<T: Send + Sync> OptionModel<T> {
fn new(value: T) -> Self {
let rw_signal = RwSignal::new(Some(value));
rw_signal.into()
}
fn new_option(value: Option<T>) -> Self {
let rw_signal = RwSignal::new(value);
rw_signal.into()
}
pub fn with<O>(&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<O>(&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<T: Send + Sync + Clone> OptionModel<T> {
pub fn get(&self) -> Option<T> {
match self {
Self::T(read, _, _) => Some(read.get()),
Self::Option(read, _, _) => read.get(),
}
}
pub fn get_untracked(&self) -> Option<T> {
match self {
Self::T(read, _, _) => Some(read.get_untracked()),
Self::Option(read, _, _) => read.get_untracked(),
}
}
}
impl<T: Send + Sync + Clone + Default> OptionModel<T> {
pub fn set(&self, value: Option<T>) {
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<T: Send + Sync> From<T> for OptionModel<T> {
fn from(value: T) -> Self {
Self::new(value)
}
}
impl<T: Send + Sync> From<RwSignal<T>> for OptionModel<T> {
fn from(rw_signal: RwSignal<T>) -> Self {
let (read, write) = rw_signal.split();
Self::T(read.into(), write, None)
}
}
impl<T: Send + Sync> From<RwSignal<Option<T>>> for OptionModel<T> {
fn from(rw_signal: RwSignal<Option<T>>) -> Self {
let (read, write) = rw_signal.split();
Self::Option(read.into(), write, None)
}
}
impl<T> From<(Signal<T>, WriteSignal<T>)> for OptionModel<T> {
fn from((read, write): (Signal<T>, WriteSignal<T>)) -> Self {
Self::T(read, write, None)
}
}
impl<T> From<(Signal<Option<T>>, WriteSignal<Option<T>>)> for OptionModel<T> {
fn from((read, write): (Signal<Option<T>>, WriteSignal<Option<T>>)) -> Self {
Self::Option(read, write, None)
}
}
impl<T: Send + Sync> From<(ReadSignal<T>, WriteSignal<T>)> for OptionModel<T> {
fn from((read, write): (ReadSignal<T>, WriteSignal<T>)) -> Self {
Self::T(read.into(), write, None)
}
}
impl<T: Send + Sync> From<(ReadSignal<Option<T>>, WriteSignal<Option<T>>)> for OptionModel<T> {
fn from((read, write): (ReadSignal<Option<T>>, WriteSignal<Option<T>>)) -> Self {
Self::Option(read.into(), write, None)
}
}
impl<T: Send + Sync> From<(Memo<T>, WriteSignal<T>)> for OptionModel<T> {
fn from((read, write): (Memo<T>, WriteSignal<T>)) -> Self {
Self::T(read.into(), write, None)
}
}
impl<T: Send + Sync> From<(Memo<Option<T>>, WriteSignal<Option<T>>)> for OptionModel<T> {
fn from((read, write): (Memo<Option<T>>, WriteSignal<Option<T>>)) -> Self {
Self::Option(read.into(), write, None)
}
}
impl<T: Default + Send + Sync> From<(Option<T>, WriteSignal<T>)> for OptionModel<T> {
fn from((read, write): (Option<T>, WriteSignal<T>)) -> Self {
let mut model = Self::new(read.unwrap_or_default());
if let OptionModel::T(_, _, on_write) = &mut model {
*on_write = Some(write);
}
model
}
}
impl<T: Default + Send + Sync> From<(Option<Option<T>>, WriteSignal<Option<T>>)>
for OptionModel<T>
{
fn from((read, write): (Option<Option<T>>, WriteSignal<Option<T>>)) -> Self {
let mut model = Self::new_option(read.unwrap_or_default());
if let OptionModel::Option(_, _, on_write) = &mut model {
*on_write = Some(write);
}
model
}
}