mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
feat: adds OptionModel
This commit is contained in:
parent
a7918ef2df
commit
a653960040
13 changed files with 228 additions and 34 deletions
|
@ -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! {
|
||||
<Calendar value />
|
||||
<Space vertical=true>
|
||||
<Calendar value />
|
||||
<Calendar value=option_value />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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! {
|
||||
<DatePicker value/>
|
||||
<Space vertical=true>
|
||||
<DatePicker value/>
|
||||
<DatePicker value=option_value/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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! {
|
||||
<RadioGroup value>
|
||||
<Radio value="a" label="Apple"/>
|
||||
<Radio value="o" label="Orange"/>
|
||||
</RadioGroup>
|
||||
<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>
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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! {
|
||||
<TimePicker value />
|
||||
<Space vertical=true>
|
||||
<TimePicker value />
|
||||
<TimePicker value=option_value />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
mod option_model;
|
||||
|
||||
pub use option_model::OptionModel;
|
||||
|
||||
use leptos::reactive_graph::{
|
||||
computed::Memo,
|
||||
signal::{ReadSignal, RwSignal, WriteSignal},
|
||||
|
|
171
thaw_utils/src/signals/model/option_model.rs
Normal file
171
thaw_utils/src/signals/model/option_model.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue