Feat/model (#92)

* feat: add Model

* feat: RwSignal is migrated to Model
This commit is contained in:
luoxiaozero 2024-01-29 10:57:55 +08:00
parent 1cba68520a
commit b2f68906df
22 changed files with 248 additions and 46 deletions

View file

@ -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<String>,
#[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] placeholder: MaybeSignal<String>,
#[prop(optional, into)] options: MaybeSignal<Vec<AutoCompleteOption>>,
#[prop(optional, into)] clear_after_select: MaybeSignal<bool>,

View file

@ -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<String>,
#[prop(optional, into)] value: RwSignal<Option<NaiveDate>>,
#[prop(optional, into)] value: Model<Option<NaiveDate>>,
) -> 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<Option<NaiveDate>>,
value: Model<Option<NaiveDate>>,
index: usize,
date: CalendarItemDate,
) -> impl IntoView {

View file

@ -1,16 +1,17 @@
use crate::utils::Model;
use leptos::*;
use std::collections::HashSet;
#[component]
pub fn CheckboxGroup(
#[prop(optional, into)] value: RwSignal<HashSet<String>>,
#[prop(optional, into)] value: Model<HashSet<String>>,
children: Children,
) -> impl IntoView {
view! { <Provider value=CheckboxGroupInjection(value) children/> }
}
#[derive(Clone)]
pub(crate) struct CheckboxGroupInjection(pub RwSignal<HashSet<String>>);
pub(crate) struct CheckboxGroupInjection(pub Model<HashSet<String>>);
pub(crate) fn use_checkbox_group() -> CheckboxGroupInjection {
expect_context()

View file

@ -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<bool>,
#[prop(optional, into)] value: Model<bool>,
#[prop(optional, into)] class: MaybeSignal<String>,
children: Children,
) -> impl IntoView {
@ -44,7 +44,7 @@ pub fn Checkbox(
>
<input class="thaw-checkbox__input" type="checkbox"/>
<div class="thaw-checkbox__dot">
<If cond=value>
<If cond=value.signal()>
<Then slot>
<Icon icon=icondata::AiCheckOutlined style="color: white"/>
</Then>

View file

@ -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<String>,
#[prop(optional, into)] value: RwSignal<HashSet<String>>,
#[prop(optional, into)] value: Model<HashSet<String>>,
#[prop(optional)] accordion: bool,
children: Children,
) -> impl IntoView {
@ -42,7 +42,7 @@ pub fn Collapse(
#[derive(Clone)]
pub(crate) struct CollapseInjection {
pub value: RwSignal<HashSet<String>>,
pub value: Model<HashSet<String>>,
pub accordion: bool,
}

View file

@ -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<RGBA>,
#[prop(optional, into)] value: Model<RGBA>,
#[prop(optional, into)] class: MaybeSignal<String>,
) -> impl IntoView {
mount_style("color-picker", include_str!("./color-picker.css"));

View file

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

View file

@ -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<bool>,
#[prop(into)] show: Model<bool>,
#[prop(optional, into)] title: MaybeSignal<String>,
#[prop(optional, into)] placement: MaybeSignal<DrawerPlacement>,
#[prop(default = MaybeSignal::Static("520px".to_string()), into)] width: MaybeSignal<String>,

View file

@ -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<String>,
#[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] allow_value: Option<Callback<String, bool>>,
#[prop(optional, into)] variant: MaybeSignal<InputVariant>,
#[prop(optional, into)] placeholder: MaybeSignal<String>,

View file

@ -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<String>,
#[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] allow_value: Option<Callback<String, bool>>,
#[prop(optional, into)] placeholder: MaybeSignal<String>,
#[prop(optional, into)] on_focus: Option<Callback<ev::FocusEvent>>,

View file

@ -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<T>(
#[prop(optional, into)] value: RwSignal<T>,
#[prop(optional, into)] value: Model<T>,
#[prop(optional, into)] placeholder: MaybeSignal<String>,
#[prop(into)] step: MaybeSignal<T>,
#[prop(optional, into)] disabled: MaybeSignal<bool>,

View file

@ -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<String>,
#[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] class: MaybeSignal<String>,
children: Children,
) -> impl IntoView {
@ -22,7 +22,7 @@ pub fn Menu(
}
#[derive(Clone)]
pub(crate) struct MenuInjection(pub RwSignal<String>);
pub(crate) struct MenuInjection(pub Model<String>);
pub(crate) fn use_menu() -> MenuInjection {
expect_context()

View file

@ -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<String>,
#[prop(optional, into)] value: Model<String>,
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<String>);
pub(crate) struct TabbarInjection(pub Model<String>);
pub(crate) fn use_tabbar() -> TabbarInjection {
expect_context()

View file

@ -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<bool>,
#[prop(into)] show: Model<bool>,
#[prop(optional, into)] title: MaybeSignal<String>,
children: Children,
#[prop(optional)] modal_footer: Option<ModalFooter>,

View file

@ -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<bool>,
#[prop(optional, into)] value: Model<bool>,
#[prop(optional, into)] class: MaybeSignal<String>,
children: Children,
) -> impl IntoView {

View file

@ -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<T> {
#[component]
pub fn Select<T>(
#[prop(optional, into)] value: RwSignal<Option<T>>,
#[prop(optional, into)] value: Model<Option<T>>,
#[prop(optional, into)] options: MaybeSignal<Vec<SelectOption<T>>>,
#[prop(optional, into)] class: MaybeSignal<String>,
) -> impl IntoView

View file

@ -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<f64>,
#[prop(optional, into)] value: Model<f64>,
#[prop(default = MaybeSignal::Static(100f64), into)] max: MaybeSignal<f64>,
#[prop(optional, into)] step: MaybeSignal<f64>,
#[prop(optional, into)] class: MaybeSignal<String>,

View file

@ -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<bool>,
#[prop(optional, into)] value: Model<bool>,
#[prop(optional, into)] class: MaybeSignal<String>,
) -> impl IntoView {
mount_style("switch", include_str!("./switch.css"));

View file

@ -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<String>,
#[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] class: MaybeSignal<String>,
children: Children,
) -> impl IntoView {
@ -30,7 +30,7 @@ pub fn Tabs(
#[component]
fn TabsInner(
value: RwSignal<String>,
value: Model<String>,
tab_options_vec: RwSignal<Vec<TabOption>>,
#[prop(optional, into)] class: MaybeSignal<String>,
children: Children,
@ -161,7 +161,7 @@ pub(crate) struct TabsLabelLine {
#[derive(Clone)]
pub(crate) struct TabsInjection {
active_key: RwSignal<String>,
active_key: Model<String>,
tab_options_vec: RwSignal<Vec<TabOption>>,
}

View file

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

View file

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

198
thaw/src/utils/model.rs Normal file
View file

@ -0,0 +1,198 @@
use leptos::{
Memo, ReadSignal, RwSignal, Signal, SignalGet, SignalGetUntracked, SignalSet, SignalUpdate,
SignalWith, SignalWithUntracked, WriteSignal,
};
pub struct Model<T>
where
T: 'static,
{
read: Signal<T>,
write: WriteSignal<T>,
on_write: Option<WriteSignal<T>>,
}
impl<T: Default> Default for Model<T> {
fn default() -> Self {
RwSignal::new(Default::default()).into()
}
}
impl<T> Clone for Model<T> {
fn clone(&self) -> Self {
Self {
read: self.read.clone(),
write: self.write.clone(),
on_write: self.on_write.clone(),
}
}
}
impl<T> Copy for Model<T> {}
impl<T> Model<T> {
fn new(value: T) -> Self {
let rw_signal = RwSignal::new(value);
rw_signal.into()
}
pub fn signal(&self) -> Signal<T> {
self.read.clone()
}
}
impl<T: Clone> SignalGet for Model<T> {
type Value = T;
fn get(&self) -> Self::Value {
self.read.get()
}
fn try_get(&self) -> Option<Self::Value> {
self.read.try_get()
}
}
impl<T: Clone> SignalGetUntracked for Model<T> {
type Value = T;
fn get_untracked(&self) -> Self::Value {
self.read.get_untracked()
}
fn try_get_untracked(&self) -> Option<Self::Value> {
self.read.try_get_untracked()
}
}
impl<T: Clone> SignalSet for Model<T> {
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<Self::Value> {
if let Some(on_write) = self.on_write.as_ref() {
on_write.try_set(new_value.clone());
}
self.write.try_set(new_value)
}
}
impl<T> SignalWith for Model<T> {
type Value = T;
fn with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> O {
self.read.with(f)
}
fn try_with<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> {
self.read.try_with(f)
}
}
impl<T> SignalWithUntracked for Model<T> {
type Value = T;
fn with_untracked<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> O {
self.read.with_untracked(f)
}
fn try_with_untracked<O>(&self, f: impl FnOnce(&Self::Value) -> O) -> Option<O> {
self.read.try_with_untracked(f)
}
}
impl<T> SignalUpdate for Model<T> {
type Value = T;
fn update(&self, f: impl FnOnce(&mut Self::Value)) {
self.write.update(f);
}
fn try_update<O>(&self, f: impl FnOnce(&mut Self::Value) -> O) -> Option<O> {
self.write.try_update(f)
}
}
impl<T> From<T> for Model<T> {
fn from(value: T) -> Self {
Self::new(value)
}
}
impl<T> From<RwSignal<T>> for Model<T> {
fn from(rw_signal: RwSignal<T>) -> Self {
let (read, write) = rw_signal.split();
Self {
read: read.into(),
write,
on_write: None,
}
}
}
impl<T> From<(ReadSignal<T>, WriteSignal<T>)> for Model<T> {
fn from((read, write): (ReadSignal<T>, WriteSignal<T>)) -> Self {
Self {
read: read.into(),
write,
on_write: None,
}
}
}
impl<T> From<(Memo<T>, WriteSignal<T>)> for Model<T> {
fn from((read, write): (Memo<T>, WriteSignal<T>)) -> Self {
Self {
read: read.into(),
write,
on_write: None,
}
}
}
impl<T: Default> From<(Option<T>, WriteSignal<T>)> for Model<T> {
fn from((read, write): (Option<T>, WriteSignal<T>)) -> 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<i32> = 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<i32> = 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<i32> = (read, write).into();
assert_eq!(modal.get_untracked(), 0);
modal.set(1);
assert_eq!(modal.get_untracked(), 1);
runtime.dispose();
}
}