mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
refactor: OptionModel and VecModel with
This commit is contained in:
parent
3bba024540
commit
d33b4e27e8
12 changed files with 104 additions and 67 deletions
|
@ -2,7 +2,7 @@ use crate::{Button, ButtonGroup};
|
||||||
use chrono::{Datelike, Days, Local, Month, Months, NaiveDate};
|
use chrono::{Datelike, Days, Local, Month, Months, NaiveDate};
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use thaw_utils::{class_list, mount_style, OptionModel};
|
use thaw_utils::{class_list, mount_style, OptionModel, OptionModelWithValue};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Calendar(
|
pub fn Calendar(
|
||||||
|
@ -136,7 +136,12 @@ fn CalendarItem(
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let is_selected = Memo::new({
|
let is_selected = Memo::new({
|
||||||
let date = date.clone();
|
let date = date.clone();
|
||||||
move |_| value.with(|value_date| value_date == Some(date.deref()))
|
move |_| value.with(|value_date| {
|
||||||
|
match value_date {
|
||||||
|
OptionModelWithValue::T(v) => v == date.deref(),
|
||||||
|
OptionModelWithValue::Option(v) => v.as_ref() == Some(date.deref()),
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
let weekday_str = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
let weekday_str = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
let on_click = {
|
let on_click = {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::_aria::use_active_descendant;
|
||||||
use leptos::{context::Provider, ev, html, prelude::*};
|
use leptos::{context::Provider, ev, html, prelude::*};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use thaw_components::{Binder, Follower, FollowerPlacement, FollowerWidth};
|
use thaw_components::{Binder, Follower, FollowerPlacement, FollowerWidth};
|
||||||
use thaw_utils::{add_event_listener, class_list, mount_style, Model, VecModel};
|
use thaw_utils::{add_event_listener, class_list, mount_style, Model, VecModel, VecModelWithValue};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Combobox(
|
pub fn Combobox(
|
||||||
|
@ -34,10 +34,9 @@ pub fn Combobox(
|
||||||
let is_show_clear_icon = Memo::new(move |_| {
|
let is_show_clear_icon = Memo::new(move |_| {
|
||||||
if clearable {
|
if clearable {
|
||||||
selected_options.with(|options| match options {
|
selected_options.with(|options| match options {
|
||||||
(None, None, Some(v)) => !v.is_empty(),
|
VecModelWithValue::T(v) => !v.is_empty(),
|
||||||
(None, Some(v), None) => v.is_some(),
|
VecModelWithValue::Option(v) => v.is_some(),
|
||||||
(Some(v), None, None) => !v.is_empty(),
|
VecModelWithValue::Vec(v) => !v.is_empty(),
|
||||||
_ => unreachable!(),
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -94,16 +93,15 @@ pub fn Combobox(
|
||||||
let on_input = move |ev| {
|
let on_input = move |ev| {
|
||||||
let input_value = event_target_value(&ev);
|
let input_value = event_target_value(&ev);
|
||||||
if selected_options.with_untracked(|options| match options {
|
if selected_options.with_untracked(|options| match options {
|
||||||
(None, None, Some(_)) => false,
|
VecModelWithValue::T(v) => v != &input_value,
|
||||||
(None, Some(v), None) => {
|
VecModelWithValue::Option(v) => {
|
||||||
if let Some(v) = v.as_ref() {
|
if let Some(v) = v.as_ref() {
|
||||||
v != &input_value
|
v != &input_value
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Some(v), None, None) => v != &input_value,
|
VecModelWithValue::Vec(_) => false,
|
||||||
_ => unreachable!(),
|
|
||||||
}) {
|
}) {
|
||||||
selected_options.set(vec![]);
|
selected_options.set(vec![]);
|
||||||
}
|
}
|
||||||
|
@ -125,18 +123,17 @@ pub fn Combobox(
|
||||||
let active_descendant_controller = active_descendant_controller.clone();
|
let active_descendant_controller = active_descendant_controller.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
selected_options.with_untracked(|options| match options {
|
selected_options.with_untracked(|options| match options {
|
||||||
(None, None, Some(_)) => value.set(String::new()),
|
VecModelWithValue::T(v) => {
|
||||||
(None, Some(v), None) => {
|
|
||||||
if v.is_none() {
|
|
||||||
value.set(String::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Some(v), None, None) => {
|
|
||||||
if v.is_empty() {
|
if v.is_empty() {
|
||||||
value.set(String::new())
|
value.set(String::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
VecModelWithValue::Option(v) => {
|
||||||
|
if v.is_none() {
|
||||||
|
value.set(String::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VecModelWithValue::Vec(_) => value.set(String::new()),
|
||||||
});
|
});
|
||||||
active_descendant_controller.blur();
|
active_descendant_controller.blur();
|
||||||
}
|
}
|
||||||
|
@ -297,16 +294,15 @@ impl ComboboxInjection {
|
||||||
|
|
||||||
pub fn is_selected(&self, value: &String) -> bool {
|
pub fn is_selected(&self, value: &String) -> bool {
|
||||||
self.selected_options.with(|options| match options {
|
self.selected_options.with(|options| match options {
|
||||||
(None, None, Some(v)) => v.contains(value),
|
VecModelWithValue::T(v) => v == value,
|
||||||
(None, Some(v), None) => {
|
VecModelWithValue::Option(v) => {
|
||||||
if let Some(v) = v.as_ref() {
|
if let Some(v) = v.as_ref() {
|
||||||
v == value
|
v == value
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Some(v), None, None) => v == value,
|
VecModelWithValue::Vec(v) => v.contains(value),
|
||||||
_ => unreachable!(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use chrono::NaiveDate;
|
||||||
use leptos::{html, prelude::*};
|
use leptos::{html, prelude::*};
|
||||||
use panel::{Panel, PanelRef};
|
use panel::{Panel, PanelRef};
|
||||||
use thaw_components::{Binder, Follower, FollowerPlacement};
|
use thaw_components::{Binder, Follower, FollowerPlacement};
|
||||||
use thaw_utils::{class_list, mount_style, now_date, ComponentRef, OptionModel};
|
use thaw_utils::{class_list, mount_style, now_date, ComponentRef, OptionModel, OptionModelWithValue};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn DatePicker(
|
pub fn DatePicker(
|
||||||
|
@ -20,9 +20,13 @@ pub fn DatePicker(
|
||||||
let show_date_format = "%Y-%m-%d";
|
let show_date_format = "%Y-%m-%d";
|
||||||
let update_show_date_text = move || {
|
let update_show_date_text = move || {
|
||||||
value.with_untracked(move |date| {
|
value.with_untracked(move |date| {
|
||||||
let text = date.map_or(String::new(), |date| {
|
let text = match date {
|
||||||
date.format(show_date_format).to_string()
|
OptionModelWithValue::T(v) => v.format(show_date_format).to_string(),
|
||||||
});
|
OptionModelWithValue::Option(v) => v.map_or(String::new(), |date| {
|
||||||
|
date.format(show_date_format).to_string()
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
show_date_text.set(text);
|
show_date_text.set(text);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::{FieldContextInjection, FieldInjection, FieldValidationState};
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use send_wrapper::SendWrapper;
|
use send_wrapper::SendWrapper;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use thaw_utils::{Model, OptionModel};
|
use thaw_utils::{Model, OptionModel, OptionModelWithValue};
|
||||||
|
|
||||||
type RuleValidator<T> = Box<dyn Fn(&T, Signal<Option<String>>) -> Result<(), FieldValidationState>>;
|
type RuleValidator<T> = Box<dyn Fn(&T, Signal<Option<String>>) -> Result<(), FieldValidationState>>;
|
||||||
|
|
||||||
|
@ -112,14 +112,20 @@ impl<T: Send + Sync> RuleValueWithUntracked<T> for Model<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Send + Sync + Clone> RuleValueWithUntracked<Option<T>> for OptionModel<T> {
|
impl RuleValueWithUntracked<Option<String>> for OptionModel<String> {
|
||||||
fn value_with_untracked(
|
fn value_with_untracked(
|
||||||
&self,
|
&self,
|
||||||
f: impl FnOnce(&Option<T>) -> Result<(), FieldValidationState>,
|
f: impl FnOnce(&Option<String>) -> Result<(), FieldValidationState>,
|
||||||
) -> Result<(), FieldValidationState> {
|
) -> Result<(), FieldValidationState> {
|
||||||
self.with_untracked(move |v| {
|
self.with_untracked(move |v| match v {
|
||||||
let v = v.map(|v| v.clone());
|
OptionModelWithValue::T(v) => {
|
||||||
f(&v)
|
if v.is_empty() {
|
||||||
|
f(&None)
|
||||||
|
} else {
|
||||||
|
f(&Some(v.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OptionModelWithValue::Option(v) => f(v),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::NavDrawerInjection;
|
||||||
use crate::Icon;
|
use crate::Icon;
|
||||||
use leptos::{either::Either, html, prelude::*};
|
use leptos::{either::Either, html, prelude::*};
|
||||||
use thaw_components::CSSTransition;
|
use thaw_components::CSSTransition;
|
||||||
use thaw_utils::{class_list, StoredMaybeSignal};
|
use thaw_utils::{class_list, StoredMaybeSignal, VecModelWithValue};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn NavCategory(
|
pub fn NavCategory(
|
||||||
|
@ -18,10 +18,9 @@ pub fn NavCategory(
|
||||||
.selected_category_value
|
.selected_category_value
|
||||||
.with(|selected_category_value| {
|
.with(|selected_category_value| {
|
||||||
value.with(|value| match selected_category_value {
|
value.with(|value| match selected_category_value {
|
||||||
(None, None, Some(v)) => v.contains(value),
|
VecModelWithValue::T(v) => v == value,
|
||||||
(None, Some(v), None) => v.as_ref() == Some(value),
|
VecModelWithValue::Option(v) => v.as_ref() == Some(value),
|
||||||
(Some(v), None, None) => v == value,
|
VecModelWithValue::Vec(v) => v.contains(value),
|
||||||
_ => unreachable!(),
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::NavDrawerInjection;
|
use super::NavDrawerInjection;
|
||||||
use crate::Icon;
|
use crate::Icon;
|
||||||
use leptos::{either::Either, prelude::*};
|
use leptos::{either::Either, prelude::*};
|
||||||
use thaw_utils::{class_list, StoredMaybeSignal};
|
use thaw_utils::{class_list, OptionModelWithValue, StoredMaybeSignal};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn NavItem(
|
pub fn NavItem(
|
||||||
|
@ -17,7 +17,12 @@ pub fn NavItem(
|
||||||
let value = value.get_untracked();
|
let value = value.get_untracked();
|
||||||
if nav_drawer
|
if nav_drawer
|
||||||
.selected_value
|
.selected_value
|
||||||
.with_untracked(|selected_value| selected_value != Some(&value))
|
.with_untracked(|selected_value|
|
||||||
|
match selected_value {
|
||||||
|
OptionModelWithValue::T(v) => v != &value,
|
||||||
|
OptionModelWithValue::Option(v) => v.as_ref() != Some(&value),
|
||||||
|
}
|
||||||
|
)
|
||||||
{
|
{
|
||||||
nav_drawer.selected_value.set(Some(value));
|
nav_drawer.selected_value.set(Some(value));
|
||||||
}
|
}
|
||||||
|
@ -39,7 +44,10 @@ pub fn NavItem(
|
||||||
let selected = Memo::new(move |_| {
|
let selected = Memo::new(move |_| {
|
||||||
nav_drawer
|
nav_drawer
|
||||||
.selected_value
|
.selected_value
|
||||||
.with(|selected_value| value.with(|value| selected_value == Some(value)))
|
.with(|selected_value| value.with(|value| match selected_value {
|
||||||
|
OptionModelWithValue::T(v) => v == value,
|
||||||
|
OptionModelWithValue::Option(v) => v.as_ref() == Some(&value),
|
||||||
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(href) = href {
|
if let Some(href) = href {
|
||||||
|
|
|
@ -4,7 +4,7 @@ pub use radio_group::{RadioGroup, RadioGroupRule, RadioGroupRuleTrigger};
|
||||||
|
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use radio_group::RadioGroupInjection;
|
use radio_group::RadioGroupInjection;
|
||||||
use thaw_utils::{class_list, mount_style};
|
use thaw_utils::{class_list, mount_style, OptionModelWithValue};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Radio(
|
pub fn Radio(
|
||||||
|
@ -26,7 +26,10 @@ pub fn Radio(
|
||||||
let group = group.clone();
|
let group = group.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
item_value
|
item_value
|
||||||
.with_value(|value| group.value.with(|group_value| group_value == Some(value)))
|
.with_value(|value| group.value.with(|group_value| match group_value {
|
||||||
|
OptionModelWithValue::T(v) => v == value,
|
||||||
|
OptionModelWithValue::Option(v) => v.as_ref() == Some(value),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
use chrono::{Local, NaiveTime, Timelike};
|
use chrono::{Local, NaiveTime, Timelike};
|
||||||
use leptos::{html, prelude::*};
|
use leptos::{html, prelude::*};
|
||||||
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement};
|
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement};
|
||||||
use thaw_utils::{class_list, mount_style, ArcOneCallback, ComponentRef, OptionModel};
|
use thaw_utils::{class_list, mount_style, ArcOneCallback, ComponentRef, OptionModel, OptionModelWithValue};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn TimePicker(
|
pub fn TimePicker(
|
||||||
|
@ -23,9 +23,13 @@ pub fn TimePicker(
|
||||||
let show_time_text = RwSignal::new(String::new());
|
let show_time_text = RwSignal::new(String::new());
|
||||||
let update_show_time_text = move || {
|
let update_show_time_text = move || {
|
||||||
value.with_untracked(move |time| {
|
value.with_untracked(move |time| {
|
||||||
let text = time.as_ref().map_or(String::new(), |time| {
|
let text = match time {
|
||||||
time.format(show_time_format).to_string()
|
OptionModelWithValue::T(v) => v.format(show_time_format).to_string(),
|
||||||
});
|
OptionModelWithValue::Option(v) => v.map_or(String::new(), |time| {
|
||||||
|
time.format(show_time_format).to_string()
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
show_time_text.set(text);
|
show_time_text.set(text);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,6 @@ mod signal_watch;
|
||||||
mod stored_maybe_signal;
|
mod stored_maybe_signal;
|
||||||
|
|
||||||
pub use component_ref::ComponentRef;
|
pub use component_ref::ComponentRef;
|
||||||
pub use model::{Model, OptionModel, VecModel};
|
pub use model::{Model, OptionModel, VecModel, VecModelWithValue, OptionModelWithValue};
|
||||||
pub use signal_watch::SignalWatch;
|
pub use signal_watch::SignalWatch;
|
||||||
pub use stored_maybe_signal::StoredMaybeSignal;
|
pub use stored_maybe_signal::StoredMaybeSignal;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
mod option_model;
|
mod option_model;
|
||||||
mod vec_model;
|
mod vec_model;
|
||||||
|
|
||||||
pub use option_model::OptionModel;
|
pub use option_model::{OptionModel, OptionModelWithValue};
|
||||||
pub use vec_model::VecModel;
|
pub use vec_model::{VecModel, VecModelWithValue};
|
||||||
|
|
||||||
use leptos::reactive_graph::{
|
use leptos::reactive_graph::{
|
||||||
computed::Memo,
|
computed::Memo,
|
||||||
|
|
|
@ -48,21 +48,26 @@ impl<T: Send + Sync> OptionModel<T> {
|
||||||
rw_signal.into()
|
rw_signal.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with<O>(&self, fun: impl FnOnce(Option<&T>) -> O) -> O {
|
pub fn with<O>(&self, fun: impl FnOnce(OptionModelWithValue<T>) -> O) -> O {
|
||||||
match self {
|
match self {
|
||||||
Self::T(read, _, _) => read.with(|value| fun(Some(value))),
|
Self::T(read, _, _) => read.with(|value| fun(OptionModelWithValue::T(value))),
|
||||||
Self::Option(read, _, _) => read.with(|value| fun(value.as_ref())),
|
Self::Option(read, _, _) => read.with(|value| fun(OptionModelWithValue::Option(value))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_untracked<O>(&self, fun: impl FnOnce(Option<&T>) -> O) -> O {
|
pub fn with_untracked<O>(&self, fun: impl FnOnce(OptionModelWithValue<T>) -> O) -> O {
|
||||||
match self {
|
match self {
|
||||||
Self::T(read, _, _) => read.with_untracked(|value| fun(Some(value))),
|
Self::T(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::T(value))),
|
||||||
Self::Option(read, _, _) => read.with_untracked(|value| fun(value.as_ref())),
|
Self::Option(read, _, _) => read.with_untracked(|value| fun(OptionModelWithValue::Option(value))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum OptionModelWithValue<'a, T> {
|
||||||
|
T(&'a T),
|
||||||
|
Option(&'a Option<T>),
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Send + Sync + Clone> OptionModel<T> {
|
impl<T: Send + Sync + Clone> OptionModel<T> {
|
||||||
pub fn get(&self) -> Option<T> {
|
pub fn get(&self) -> Option<T> {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -69,27 +69,34 @@ impl<T: Send + Sync> VecModel<T> {
|
||||||
|
|
||||||
pub fn with<O>(
|
pub fn with<O>(
|
||||||
&self,
|
&self,
|
||||||
fun: impl FnOnce((Option<&T>, Option<&Option<T>>, Option<&Vec<T>>)) -> O,
|
fun: impl FnOnce(VecModelWithValue<T>) -> O,
|
||||||
) -> O {
|
) -> O {
|
||||||
match self {
|
match self {
|
||||||
Self::T(read, _, _) => read.with(|value| fun((Some(value), None, None))),
|
Self::T(read, _, _) => read.with(|value| fun(VecModelWithValue::T(value))),
|
||||||
Self::Option(read, _, _) => read.with(|value| fun((None, Some(value), None))),
|
Self::Option(read, _, _) => read.with(|value| fun(VecModelWithValue::Option(value))),
|
||||||
Self::Vec(read, _, _) => read.with(|value| fun((None, None, Some(value)))),
|
Self::Vec(read, _, _) => read.with(|value| fun(VecModelWithValue::Vec(value))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_untracked<O>(
|
pub fn with_untracked<O>(&self, fun: impl FnOnce(VecModelWithValue<T>) -> O) -> O {
|
||||||
&self,
|
|
||||||
fun: impl FnOnce((Option<&T>, Option<&Option<T>>, Option<&Vec<T>>)) -> O,
|
|
||||||
) -> O {
|
|
||||||
match self {
|
match self {
|
||||||
Self::T(read, _, _) => read.with_untracked(|value| fun((Some(value), None, None))),
|
Self::T(read, _, _) => read.with_untracked(|value| fun(VecModelWithValue::T(value))),
|
||||||
Self::Option(read, _, _) => read.with_untracked(|value| fun((None, Some(value), None))),
|
Self::Option(read, _, _) => {
|
||||||
Self::Vec(read, _, _) => read.with_untracked(|value| fun((None, None, Some(value)))),
|
read.with_untracked(|value| fun(VecModelWithValue::Option(value)))
|
||||||
|
}
|
||||||
|
Self::Vec(read, _, _) => {
|
||||||
|
read.with_untracked(|value| fun(VecModelWithValue::Vec(value)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum VecModelWithValue<'a, T> {
|
||||||
|
T(&'a T),
|
||||||
|
Option(&'a Option<T>),
|
||||||
|
Vec(&'a Vec<T>),
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Send + Sync + Clone + Default> VecModel<T> {
|
impl<T: Send + Sync + Clone + Default> VecModel<T> {
|
||||||
pub fn set(&self, mut value: Vec<T>) {
|
pub fn set(&self, mut value: Vec<T>) {
|
||||||
match self {
|
match self {
|
||||||
|
|
Loading…
Add table
Reference in a new issue