diff --git a/thaw/src/auto_complete/mod.rs b/thaw/src/auto_complete/mod.rs index 80157d5..47d5154 100644 --- a/thaw/src/auto_complete/mod.rs +++ b/thaw/src/auto_complete/mod.rs @@ -3,7 +3,7 @@ mod theme; use crate::{ components::{Binder, Follower, FollowerPlacement, FollowerWidth}, use_theme, - utils::{class_list::class_list, mount_style, StoredMaybeSignal}, + utils::{class_list::class_list, mount_style, Model, StoredMaybeSignal}, ComponentRef, Input, InputPrefix, InputRef, InputSuffix, Theme, }; use leptos::*; @@ -27,7 +27,7 @@ pub struct AutoCompleteSuffix { #[component] pub fn AutoComplete( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] placeholder: MaybeSignal, #[prop(optional, into)] options: MaybeSignal>, #[prop(optional, into)] clear_after_select: MaybeSignal, diff --git a/thaw/src/calendar/mod.rs b/thaw/src/calendar/mod.rs index 78b3f6e..1d60ff9 100644 --- a/thaw/src/calendar/mod.rs +++ b/thaw/src/calendar/mod.rs @@ -3,7 +3,7 @@ mod theme; use crate::{ chrono::{Datelike, Days, Local, NaiveDate}, use_theme, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Button, ButtonGroup, ButtonVariant, Theme, }; use chrono::{Month, Months}; @@ -14,7 +14,7 @@ pub use theme::CalendarTheme; #[component] pub fn Calendar( #[prop(optional, into)] class: MaybeSignal, - #[prop(optional, into)] value: RwSignal>, + #[prop(optional, into)] value: Model>, ) -> impl IntoView { mount_style("calendar", include_str!("./calendar.css")); let theme = use_theme(Theme::light); @@ -170,7 +170,7 @@ pub fn Calendar( #[component] fn CalendarItem( - value: RwSignal>, + value: Model>, index: usize, date: CalendarItemDate, ) -> impl IntoView { diff --git a/thaw/src/checkbox/checkbox_group.rs b/thaw/src/checkbox/checkbox_group.rs index fda9c53..bc40798 100644 --- a/thaw/src/checkbox/checkbox_group.rs +++ b/thaw/src/checkbox/checkbox_group.rs @@ -1,16 +1,17 @@ +use crate::utils::Model; use leptos::*; use std::collections::HashSet; #[component] pub fn CheckboxGroup( - #[prop(optional, into)] value: RwSignal>, + #[prop(optional, into)] value: Model>, children: Children, ) -> impl IntoView { view! { } } #[derive(Clone)] -pub(crate) struct CheckboxGroupInjection(pub RwSignal>); +pub(crate) struct CheckboxGroupInjection(pub Model>); pub(crate) fn use_checkbox_group() -> CheckboxGroupInjection { expect_context() diff --git a/thaw/src/checkbox/mod.rs b/thaw/src/checkbox/mod.rs index b607aab..33b9326 100644 --- a/thaw/src/checkbox/mod.rs +++ b/thaw/src/checkbox/mod.rs @@ -5,7 +5,7 @@ use crate::{ components::*, icon::*, theme::use_theme, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Theme, }; pub use checkbox_group::CheckboxGroup; @@ -14,7 +14,7 @@ use leptos::*; #[component] pub fn Checkbox( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] class: MaybeSignal, children: Children, ) -> impl IntoView { @@ -44,7 +44,7 @@ pub fn Checkbox( >
- + diff --git a/thaw/src/collapse/mod.rs b/thaw/src/collapse/mod.rs index 15c693f..54961a7 100644 --- a/thaw/src/collapse/mod.rs +++ b/thaw/src/collapse/mod.rs @@ -6,7 +6,7 @@ pub use theme::CollapseTheme; use crate::{ use_theme, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Theme, }; use leptos::*; @@ -15,7 +15,7 @@ use std::collections::HashSet; #[component] pub fn Collapse( #[prop(optional, into)] class: MaybeSignal, - #[prop(optional, into)] value: RwSignal>, + #[prop(optional, into)] value: Model>, #[prop(optional)] accordion: bool, children: Children, ) -> impl IntoView { @@ -42,7 +42,7 @@ pub fn Collapse( #[derive(Clone)] pub(crate) struct CollapseInjection { - pub value: RwSignal>, + pub value: Model>, pub accordion: bool, } diff --git a/thaw/src/color_picker/mod.rs b/thaw/src/color_picker/mod.rs index 5836f02..1f7f1c5 100644 --- a/thaw/src/color_picker/mod.rs +++ b/thaw/src/color_picker/mod.rs @@ -4,7 +4,7 @@ mod theme; use crate::{ components::{Binder, Follower, FollowerPlacement}, use_theme, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Theme, }; pub use color::*; @@ -14,7 +14,7 @@ pub use theme::ColorPickerTheme; #[component] pub fn ColorPicker( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] class: MaybeSignal, ) -> impl IntoView { mount_style("color-picker", include_str!("./color-picker.css")); diff --git a/thaw/src/date_picker/mod.rs b/thaw/src/date_picker/mod.rs index 639c8ce..c97f9da 100644 --- a/thaw/src/date_picker/mod.rs +++ b/thaw/src/date_picker/mod.rs @@ -4,7 +4,7 @@ mod theme; use crate::{ chrono::NaiveDate, components::{Binder, Follower, FollowerPlacement}, - utils::{mount_style, now_date, ComponentRef}, + utils::{mount_style, now_date, ComponentRef, Model}, Icon, Input, InputSuffix, SignalWatch, }; use leptos::*; @@ -13,7 +13,7 @@ pub use theme::DatePickerTheme; #[component] pub fn DatePicker( - #[prop(optional, into)] value: RwSignal>, + #[prop(optional, into)] value: Model>, #[prop(optional, into)] class: MaybeSignal, #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, ) -> impl IntoView { diff --git a/thaw/src/drawer/mod.rs b/thaw/src/drawer/mod.rs index 2929619..136106f 100644 --- a/thaw/src/drawer/mod.rs +++ b/thaw/src/drawer/mod.rs @@ -1,13 +1,13 @@ use crate::{ components::Teleport, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Card, }; use leptos::*; #[component] pub fn Drawer( - #[prop(into)] show: RwSignal, + #[prop(into)] show: Model, #[prop(optional, into)] title: MaybeSignal, #[prop(optional, into)] placement: MaybeSignal, #[prop(default = MaybeSignal::Static("520px".to_string()), into)] width: MaybeSignal, diff --git a/thaw/src/input/mod.rs b/thaw/src/input/mod.rs index 4140629..1c1b453 100644 --- a/thaw/src/input/mod.rs +++ b/thaw/src/input/mod.rs @@ -6,7 +6,7 @@ pub use theme::InputTheme; use crate::{ theme::{use_theme, Theme}, - utils::{class_list::class_list, mount_style, ComponentRef}, + utils::{class_list::class_list, mount_style, ComponentRef, Model}, }; use leptos::*; @@ -42,7 +42,7 @@ pub struct InputSuffix { #[component] pub fn Input( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] allow_value: Option>, #[prop(optional, into)] variant: MaybeSignal, #[prop(optional, into)] placeholder: MaybeSignal, diff --git a/thaw/src/input/text_area.rs b/thaw/src/input/text_area.rs index e476c43..51810cf 100644 --- a/thaw/src/input/text_area.rs +++ b/thaw/src/input/text_area.rs @@ -1,12 +1,12 @@ use crate::{ theme::{use_theme, Theme}, - utils::{class_list::class_list, mount_style, ComponentRef}, + utils::{class_list::class_list, mount_style, ComponentRef, Model}, }; use leptos::*; #[component] pub fn TextArea( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] allow_value: Option>, #[prop(optional, into)] placeholder: MaybeSignal, #[prop(optional, into)] on_focus: Option>, diff --git a/thaw/src/input_number/mod.rs b/thaw/src/input_number/mod.rs index ef2e55e..ec5b184 100644 --- a/thaw/src/input_number/mod.rs +++ b/thaw/src/input_number/mod.rs @@ -1,4 +1,4 @@ -use crate::utils::StoredMaybeSignal; +use crate::utils::{Model, StoredMaybeSignal}; use crate::{Button, ButtonVariant, ComponentRef, Icon, Input, InputRef, InputSuffix}; use leptos::*; use std::ops::{Add, Sub}; @@ -6,7 +6,7 @@ use std::str::FromStr; #[component] pub fn InputNumber( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] placeholder: MaybeSignal, #[prop(into)] step: MaybeSignal, #[prop(optional, into)] disabled: MaybeSignal, diff --git a/thaw/src/menu/mod.rs b/thaw/src/menu/mod.rs index b4106c1..08a8693 100644 --- a/thaw/src/menu/mod.rs +++ b/thaw/src/menu/mod.rs @@ -2,7 +2,7 @@ mod menu_group; mod menu_item; mod theme; -use crate::utils::class_list::class_list; +use crate::utils::{class_list::class_list, Model}; use leptos::*; pub use menu_group::MenuGroup; pub use menu_item::*; @@ -10,7 +10,7 @@ pub use theme::MenuTheme; #[component] pub fn Menu( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] class: MaybeSignal, children: Children, ) -> impl IntoView { @@ -22,7 +22,7 @@ pub fn Menu( } #[derive(Clone)] -pub(crate) struct MenuInjection(pub RwSignal); +pub(crate) struct MenuInjection(pub Model); pub(crate) fn use_menu() -> MenuInjection { expect_context() diff --git a/thaw/src/mobile/tabbar/mod.rs b/thaw/src/mobile/tabbar/mod.rs index c21c082..cb274de 100644 --- a/thaw/src/mobile/tabbar/mod.rs +++ b/thaw/src/mobile/tabbar/mod.rs @@ -1,14 +1,14 @@ mod tabbar_item; mod theme; -use crate::{use_theme, utils::mount_style, Theme}; +use crate::{use_theme, utils::{mount_style, Model}, Theme}; use leptos::*; pub use tabbar_item::*; pub use theme::TabbarTheme; #[component] pub fn Tabbar( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, children: Children, ) -> impl IntoView { mount_style("tabbar", include_str!("./tabbar.css")); @@ -32,7 +32,7 @@ pub fn Tabbar( } #[derive(Clone)] -pub(crate) struct TabbarInjection(pub RwSignal); +pub(crate) struct TabbarInjection(pub Model); pub(crate) fn use_tabbar() -> TabbarInjection { expect_context() diff --git a/thaw/src/modal/mod.rs b/thaw/src/modal/mod.rs index f679931..c571d4a 100644 --- a/thaw/src/modal/mod.rs +++ b/thaw/src/modal/mod.rs @@ -1,4 +1,5 @@ use crate::icon::*; +use crate::utils::Model; use crate::{ components::{OptionComp, Teleport}, utils::{mount_style, StoredMaybeSignal}, @@ -13,7 +14,7 @@ pub struct ModalFooter { #[component] pub fn Modal( - #[prop(into)] show: RwSignal, + #[prop(into)] show: Model, #[prop(optional, into)] title: MaybeSignal, children: Children, #[prop(optional)] modal_footer: Option, diff --git a/thaw/src/radio/mod.rs b/thaw/src/radio/mod.rs index 82128eb..03e87b7 100644 --- a/thaw/src/radio/mod.rs +++ b/thaw/src/radio/mod.rs @@ -1,13 +1,13 @@ use crate::{ theme::use_theme, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Theme, }; use leptos::*; #[component] pub fn Radio( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] class: MaybeSignal, children: Children, ) -> impl IntoView { diff --git a/thaw/src/select/mod.rs b/thaw/src/select/mod.rs index 267ae49..dfd0478 100644 --- a/thaw/src/select/mod.rs +++ b/thaw/src/select/mod.rs @@ -3,7 +3,7 @@ mod theme; use crate::{ components::{Binder, Follower, FollowerPlacement, FollowerWidth}, theme::use_theme, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Theme, }; use leptos::*; @@ -18,7 +18,7 @@ pub struct SelectOption { #[component] pub fn Select( - #[prop(optional, into)] value: RwSignal>, + #[prop(optional, into)] value: Model>, #[prop(optional, into)] options: MaybeSignal>>, #[prop(optional, into)] class: MaybeSignal, ) -> impl IntoView diff --git a/thaw/src/slider/mod.rs b/thaw/src/slider/mod.rs index dbbd1fc..f12d01c 100644 --- a/thaw/src/slider/mod.rs +++ b/thaw/src/slider/mod.rs @@ -4,7 +4,7 @@ mod theme; use crate::{ components::OptionComp, theme::use_theme, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Theme, }; use leptos::*; @@ -15,7 +15,7 @@ pub use theme::SliderTheme; #[component] pub fn Slider( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(default = MaybeSignal::Static(100f64), into)] max: MaybeSignal, #[prop(optional, into)] step: MaybeSignal, #[prop(optional, into)] class: MaybeSignal, diff --git a/thaw/src/switch/mod.rs b/thaw/src/switch/mod.rs index 271344e..0558836 100644 --- a/thaw/src/switch/mod.rs +++ b/thaw/src/switch/mod.rs @@ -2,7 +2,7 @@ mod theme; use crate::{ theme::use_theme, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Theme, }; use leptos::*; @@ -10,7 +10,7 @@ pub use theme::SwitchTheme; #[component] pub fn Switch( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] class: MaybeSignal, ) -> impl IntoView { mount_style("switch", include_str!("./switch.css")); diff --git a/thaw/src/tabs/mod.rs b/thaw/src/tabs/mod.rs index 1fce522..2e2b931 100644 --- a/thaw/src/tabs/mod.rs +++ b/thaw/src/tabs/mod.rs @@ -2,7 +2,7 @@ mod tab; use crate::{ theme::use_theme, - utils::{class_list::class_list, mount_style}, + utils::{class_list::class_list, mount_style, Model}, Theme, }; use leptos::*; @@ -11,7 +11,7 @@ pub use tab::*; #[component] pub fn Tabs( - #[prop(optional, into)] value: RwSignal, + #[prop(optional, into)] value: Model, #[prop(optional, into)] class: MaybeSignal, children: Children, ) -> impl IntoView { @@ -30,7 +30,7 @@ pub fn Tabs( #[component] fn TabsInner( - value: RwSignal, + value: Model, tab_options_vec: RwSignal>, #[prop(optional, into)] class: MaybeSignal, children: Children, @@ -161,7 +161,7 @@ pub(crate) struct TabsLabelLine { #[derive(Clone)] pub(crate) struct TabsInjection { - active_key: RwSignal, + active_key: Model, tab_options_vec: RwSignal>, } diff --git a/thaw/src/time_picker/mod.rs b/thaw/src/time_picker/mod.rs index 5350d54..b77be25 100644 --- a/thaw/src/time_picker/mod.rs +++ b/thaw/src/time_picker/mod.rs @@ -4,7 +4,7 @@ use crate::{ chrono::{Local, NaiveTime, Timelike}, components::{Binder, Follower, FollowerPlacement}, use_theme, - utils::{mount_style, ComponentRef}, + utils::{mount_style, ComponentRef, Model}, Button, ButtonSize, ButtonVariant, Icon, Input, InputSuffix, SignalWatch, Theme, }; use leptos::*; @@ -12,7 +12,7 @@ pub use theme::TimePickerTheme; #[component] pub fn TimePicker( - #[prop(optional, into)] value: RwSignal>, + #[prop(optional, into)] value: Model>, #[prop(optional, into)] class: MaybeSignal, #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, ) -> impl IntoView { diff --git a/thaw/src/utils/mod.rs b/thaw/src/utils/mod.rs index 9662c96..5b78c61 100644 --- a/thaw/src/utils/mod.rs +++ b/thaw/src/utils/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod class_list; mod component_ref; mod event_listener; +mod model; mod mount_style; mod signal; mod stored_maybe_signal; @@ -10,6 +11,7 @@ mod time; // pub use callback::AsyncCallback; pub use component_ref::{create_component_ref, ComponentRef}; pub(crate) use event_listener::*; +pub(crate) use model::Model; pub(crate) use mount_style::mount_style; pub use signal::SignalWatch; pub(crate) use stored_maybe_signal::*; diff --git a/thaw/src/utils/model.rs b/thaw/src/utils/model.rs new file mode 100644 index 0000000..95411f5 --- /dev/null +++ b/thaw/src/utils/model.rs @@ -0,0 +1,198 @@ +use leptos::{ + Memo, ReadSignal, RwSignal, Signal, SignalGet, SignalGetUntracked, SignalSet, SignalUpdate, + SignalWith, SignalWithUntracked, WriteSignal, +}; + +pub struct Model +where + T: 'static, +{ + read: Signal, + write: WriteSignal, + on_write: Option>, +} + +impl Default for Model { + fn default() -> Self { + RwSignal::new(Default::default()).into() + } +} + +impl Clone for Model { + fn clone(&self) -> Self { + Self { + read: self.read.clone(), + write: self.write.clone(), + on_write: self.on_write.clone(), + } + } +} + +impl Copy for Model {} + +impl Model { + fn new(value: T) -> Self { + let rw_signal = RwSignal::new(value); + rw_signal.into() + } + + pub fn signal(&self) -> Signal { + self.read.clone() + } +} + +impl SignalGet for Model { + type Value = T; + + fn get(&self) -> Self::Value { + self.read.get() + } + + fn try_get(&self) -> Option { + self.read.try_get() + } +} + +impl SignalGetUntracked for Model { + type Value = T; + + fn get_untracked(&self) -> Self::Value { + self.read.get_untracked() + } + + fn try_get_untracked(&self) -> Option { + self.read.try_get_untracked() + } +} + +impl SignalSet for Model { + type Value = T; + + fn set(&self, new_value: Self::Value) { + if let Some(on_write) = self.on_write.as_ref() { + on_write.set(new_value.clone()); + } + self.write.set(new_value); + } + + fn try_set(&self, new_value: Self::Value) -> Option { + if let Some(on_write) = self.on_write.as_ref() { + on_write.try_set(new_value.clone()); + } + self.write.try_set(new_value) + } +} + +impl SignalWith for Model { + type Value = T; + + fn with(&self, f: impl FnOnce(&Self::Value) -> O) -> O { + self.read.with(f) + } + + fn try_with(&self, f: impl FnOnce(&Self::Value) -> O) -> Option { + self.read.try_with(f) + } +} + +impl SignalWithUntracked for Model { + type Value = T; + + fn with_untracked(&self, f: impl FnOnce(&Self::Value) -> O) -> O { + self.read.with_untracked(f) + } + + fn try_with_untracked(&self, f: impl FnOnce(&Self::Value) -> O) -> Option { + self.read.try_with_untracked(f) + } +} + +impl SignalUpdate for Model { + type Value = T; + + fn update(&self, f: impl FnOnce(&mut Self::Value)) { + self.write.update(f); + } + + fn try_update(&self, f: impl FnOnce(&mut Self::Value) -> O) -> Option { + self.write.try_update(f) + } +} + +impl From for Model { + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl From> for Model { + fn from(rw_signal: RwSignal) -> Self { + let (read, write) = rw_signal.split(); + Self { + read: read.into(), + write, + on_write: None, + } + } +} + +impl From<(ReadSignal, WriteSignal)> for Model { + fn from((read, write): (ReadSignal, WriteSignal)) -> Self { + Self { + read: read.into(), + write, + on_write: None, + } + } +} + +impl From<(Memo, WriteSignal)> for Model { + fn from((read, write): (Memo, WriteSignal)) -> Self { + Self { + read: read.into(), + write, + on_write: None, + } + } +} + +impl From<(Option, WriteSignal)> for Model { + fn from((read, write): (Option, WriteSignal)) -> Self { + let mut modal = Self::new(read.unwrap_or_default()); + modal.on_write = Some(write.into()); + modal + } +} + +#[cfg(test)] +mod test { + use super::Model; + use leptos::*; + + #[test] + fn from() { + let runtime = create_runtime(); + + // T + let modal: Model = 0.into(); + assert_eq!(modal.get_untracked(), 0); + modal.set(1); + assert_eq!(modal.get_untracked(), 1); + + // RwSignal + let rw_signal = RwSignal::new(0); + let modal: Model = rw_signal.into(); + assert_eq!(modal.get_untracked(), 0); + modal.set(1); + assert_eq!(modal.get_untracked(), 1); + + // Read Write + let (read, write) = create_signal(0); + let modal: Model = (read, write).into(); + assert_eq!(modal.get_untracked(), 0); + modal.set(1); + assert_eq!(modal.get_untracked(), 1); + + runtime.dispose(); + } +}