feat: adds VecModel

This commit is contained in:
luoxiao 2024-07-25 15:10:49 +08:00
parent a653960040
commit f11efb4ef8
5 changed files with 343 additions and 33 deletions

View file

@ -1,7 +1,7 @@
# Combobox # Combobox
```rust demo ```rust demo
let selected_options = RwSignal::new(vec![]); let selected_options = RwSignal::new(None::<String>);
view! { view! {
<Combobox selected_options> <Combobox selected_options>
@ -17,7 +17,7 @@ view! {
let selected_options = RwSignal::new(vec![]); let selected_options = RwSignal::new(vec![]);
view! { view! {
<Combobox selected_options multiselect=true clearable=true> <Combobox selected_options clearable=true>
<ComboboxOption value="cat" text="Car" /> <ComboboxOption value="cat" text="Car" />
<ComboboxOption value="dog" text="Dog" /> <ComboboxOption value="dog" text="Dog" />
</Combobox> </Combobox>
@ -30,7 +30,7 @@ view! {
let selected_options = RwSignal::new(vec![]); let selected_options = RwSignal::new(vec![]);
view! { view! {
<Combobox selected_options multiselect=true> <Combobox selected_options>
<ComboboxOption value="cat" text="Car" /> <ComboboxOption value="cat" text="Car" />
<ComboboxOption value="dog" text="Dog" /> <ComboboxOption value="dog" text="Dog" />
</Combobox> </Combobox>

View file

@ -3,13 +3,12 @@ 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, mount_style, Model}; use thaw_utils::{add_event_listener, mount_style, Model, VecModel};
#[component] #[component]
pub fn Combobox( pub fn Combobox(
#[prop(optional, into)] value: Model<String>, #[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] selected_options: Model<Vec<String>>, #[prop(optional, into)] selected_options: VecModel<String>,
#[prop(optional)] multiselect: bool,
#[prop(optional)] clearable: bool, #[prop(optional)] clearable: bool,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
@ -23,7 +22,12 @@ pub fn Combobox(
let clear_icon_ref = NodeRef::<html::Span>::new(); let clear_icon_ref = NodeRef::<html::Span>::new();
let is_show_clear_icon = Memo::new(move |_| { let is_show_clear_icon = Memo::new(move |_| {
if clearable { if clearable {
selected_options.with(|options| !options.is_empty()) selected_options.with(|options| match options {
(None, None, Some(v)) => !v.is_empty(),
(None, Some(v), None) => v.is_some(),
(Some(v), None, None) => !v.is_empty(),
_ => unreachable!(),
})
} else { } else {
false false
} }
@ -75,21 +79,24 @@ 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 !multiselect { if selected_options.with_untracked(|options| match options {
if selected_options.with_untracked(|options| { (None, None, Some(_)) => false,
if let Some(option) = options.first() { (None, Some(v), None) => {
if option != &input_value { if let Some(v) = v.as_ref() {
return true; v != &input_value
} } else {
}
false false
}
}
(Some(v), None, None) => v != &input_value,
_ => unreachable!(),
}) { }) {
selected_options.set(vec![]); selected_options.set(vec![]);
} }
}
value.set(input_value); value.set(input_value);
}; };
let multiselect = selected_options.is_vec();
let combobox_injection = ComboboxInjection { let combobox_injection = ComboboxInjection {
value, value,
multiselect, multiselect,
@ -103,13 +110,20 @@ pub fn Combobox(
let on_blur = { let on_blur = {
let active_descendant_controller = active_descendant_controller.clone(); let active_descendant_controller = active_descendant_controller.clone();
move |_| { move |_| {
if multiselect { selected_options.with_untracked(|options| match options {
value.set(String::new()); (None, None, Some(_)) => value.set(String::new()),
} else { (None, Some(v), None) => {
if selected_options.with_untracked(|options| options.is_empty()) { if v.is_none() {
value.set(String::new()); value.set(String::new())
} }
} }
(Some(v), None, None) => {
if v.is_empty() {
value.set(String::new())
}
}
_ => unreachable!(),
});
active_descendant_controller.blur(); active_descendant_controller.blur();
} }
}; };
@ -208,7 +222,7 @@ pub fn Combobox(
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub(crate) struct ComboboxInjection { pub(crate) struct ComboboxInjection {
value: Model<String>, value: Model<String>,
selected_options: Model<Vec<String>>, selected_options: VecModel<String>,
options: StoredValue<HashMap<String, (String, String)>>, options: StoredValue<HashMap<String, (String, String)>>,
is_show_listbox: RwSignal<bool>, is_show_listbox: RwSignal<bool>,
pub multiselect: bool, pub multiselect: bool,
@ -229,23 +243,40 @@ impl ComboboxInjection {
} }
pub fn is_selected(&self, value: &String) -> bool { pub fn is_selected(&self, value: &String) -> bool {
self.selected_options self.selected_options.with(|options| match options {
.with(|options| options.contains(value)) (None, None, Some(v)) => v.contains(value),
(None, Some(v), None) => {
if let Some(v) = v.as_ref() {
v == value
} else {
false
}
}
(Some(v), None, None) => v == value,
_ => unreachable!(),
})
} }
pub fn select_option(&self, value: &String, text: &String) { pub fn select_option(&self, value: &String, text: &String) {
self.selected_options.update(|options| { self.selected_options.update(|options| match options {
if self.multiselect { (None, None, Some(v)) => {
if let Some(index) = options.iter().position(|v| v == value) { if let Some(index) = v.iter().position(|v| v == value) {
options.remove(index); v.remove(index);
return; return;
} }
options.push(value.clone()); v.push(value.clone());
} else { }
*options = vec![value.clone()]; (None, Some(v), None) => {
*v = Some(value.clone());
self.value.set(text.clone()); self.value.set(text.clone());
self.is_show_listbox.set(false); self.is_show_listbox.set(false);
} }
(Some(v), None, None) => {
*v = value.clone();
self.value.set(text.clone());
self.is_show_listbox.set(false);
}
_ => unreachable!(),
}); });
} }
} }

View file

@ -5,7 +5,7 @@ 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}; pub use model::{Model, OptionModel, VecModel};
pub use optional_maybe_signal::OptionalMaybeSignal; pub use optional_maybe_signal::OptionalMaybeSignal;
pub use signal_watch::SignalWatch; pub use signal_watch::SignalWatch;
pub use stored_maybe_signal::StoredMaybeSignal; pub use stored_maybe_signal::StoredMaybeSignal;

View file

@ -1,6 +1,8 @@
mod option_model; mod option_model;
mod vec_model;
pub use option_model::OptionModel; pub use option_model::OptionModel;
pub use vec_model::VecModel;
use leptos::reactive_graph::{ use leptos::reactive_graph::{
computed::Memo, computed::Memo,

View file

@ -0,0 +1,277 @@
use leptos::{
prelude::Update,
reactive_graph::{
computed::Memo,
signal::{ReadSignal, RwSignal, WriteSignal},
traits::{GetUntracked, Set, With, WithUntracked},
wrappers::read::Signal,
},
};
pub enum VecModel<T>
where
T: 'static,
{
T(Signal<T>, WriteSignal<T>, Option<WriteSignal<T>>),
Option(
Signal<Option<T>>,
WriteSignal<Option<T>>,
Option<WriteSignal<Option<T>>>,
),
Vec(
Signal<Vec<T>>,
WriteSignal<Vec<T>>,
Option<WriteSignal<Vec<T>>>,
),
}
impl<T: Default + Send + Sync> Default for VecModel<T> {
fn default() -> Self {
Self::new(Default::default())
}
}
impl<T> Clone for VecModel<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for VecModel<T> {}
impl<T: Send + Sync> VecModel<T> {
fn new(value: T) -> Self {
Self::new_option(Some(value))
}
fn new_option(value: Option<T>) -> Self {
let rw_signal = RwSignal::new(value);
rw_signal.into()
}
fn new_vec(value: Vec<T>) -> Self {
let rw_signal = RwSignal::new(value);
rw_signal.into()
}
pub fn is_vec(&self) -> bool {
if let VecModel::Vec(_, _, _) = self {
true
} else {
false
}
}
pub fn with<O>(
&self,
fun: impl FnOnce((Option<&T>, Option<&Option<T>>, Option<&Vec<T>>)) -> O,
) -> O {
match self {
Self::T(read, _, _) => read.with(|value| fun((Some(value), None, None))),
Self::Option(read, _, _) => read.with(|value| fun((None, Some(value), None))),
Self::Vec(read, _, _) => read.with(|value| fun((None, None, Some(value)))),
}
}
pub fn with_untracked<O>(
&self,
fun: impl FnOnce((Option<&T>, Option<&Option<T>>, Option<&Vec<T>>)) -> O,
) -> O {
match self {
Self::T(read, _, _) => read.with_untracked(|value| fun((Some(value), None, None))),
Self::Option(read, _, _) => read.with_untracked(|value| fun((None, Some(value), None))),
Self::Vec(read, _, _) => read.with_untracked(|value| fun((None, None, Some(value)))),
}
}
}
impl<T: Send + Sync + Clone + Default> VecModel<T> {
pub fn set(&self, mut value: Vec<T>) {
match self {
Self::T(read, write, on_write) => {
let value = if value.is_empty() {
Default::default()
} else {
value.remove(0)
};
write.set(value);
if let Some(on_write) = on_write.as_ref() {
on_write.set(read.get_untracked());
}
}
Self::Option(read, write, on_write) => {
let value = if value.is_empty() {
None
} else {
Some(value.remove(0))
};
write.set(value);
if let Some(on_write) = on_write.as_ref() {
on_write.set(read.get_untracked());
}
}
Self::Vec(read, write, on_write) => {
write.set(value);
if let Some(on_write) = on_write.as_ref() {
on_write.set(read.get_untracked());
}
}
}
}
pub fn update(
&self,
fun: impl FnOnce((Option<&mut T>, Option<&mut Option<T>>, Option<&mut Vec<T>>)),
) {
match self {
Self::T(read, write, on_write) => {
write.update(move |write| {
fun((Some(write), None, None));
});
if let Some(on_write) = on_write.as_ref() {
on_write.set(read.get_untracked());
}
}
Self::Option(read, write, on_write) => {
write.update(move |write| {
fun((None, Some(write), None));
});
if let Some(on_write) = on_write.as_ref() {
on_write.set(read.get_untracked());
}
}
Self::Vec(read, write, on_write) => {
write.update(move |write| {
fun((None, None, Some(write)));
});
if let Some(on_write) = on_write.as_ref() {
on_write.set(read.get_untracked());
}
}
}
}
}
impl<T: Send + Sync> From<T> for VecModel<T> {
fn from(value: T) -> Self {
Self::new(value)
}
}
impl<T: Send + Sync> From<Option<T>> for VecModel<T> {
fn from(value: Option<T>) -> Self {
Self::new_option(value)
}
}
impl<T: Send + Sync> From<Vec<T>> for VecModel<T> {
fn from(value: Vec<T>) -> Self {
Self::new_vec(value)
}
}
impl<T: Send + Sync> From<RwSignal<T>> for VecModel<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 VecModel<T> {
fn from(rw_signal: RwSignal<Option<T>>) -> Self {
let (read, write) = rw_signal.split();
Self::Option(read.into(), write, None)
}
}
impl<T: Send + Sync> From<RwSignal<Vec<T>>> for VecModel<T> {
fn from(rw_signal: RwSignal<Vec<T>>) -> Self {
let (read, write) = rw_signal.split();
Self::Vec(read.into(), write, None)
}
}
impl<T> From<(Signal<T>, WriteSignal<T>)> for VecModel<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 VecModel<T> {
fn from((read, write): (Signal<Option<T>>, WriteSignal<Option<T>>)) -> Self {
Self::Option(read, write, None)
}
}
impl<T> From<(Signal<Vec<T>>, WriteSignal<Vec<T>>)> for VecModel<T> {
fn from((read, write): (Signal<Vec<T>>, WriteSignal<Vec<T>>)) -> Self {
Self::Vec(read, write, None)
}
}
impl<T: Send + Sync> From<(ReadSignal<T>, WriteSignal<T>)> for VecModel<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 VecModel<T> {
fn from((read, write): (ReadSignal<Option<T>>, WriteSignal<Option<T>>)) -> Self {
Self::Option(read.into(), write, None)
}
}
impl<T: Send + Sync> From<(ReadSignal<Vec<T>>, WriteSignal<Vec<T>>)> for VecModel<T> {
fn from((read, write): (ReadSignal<Vec<T>>, WriteSignal<Vec<T>>)) -> Self {
Self::Vec(read.into(), write, None)
}
}
impl<T: Send + Sync> From<(Memo<T>, WriteSignal<T>)> for VecModel<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 VecModel<T> {
fn from((read, write): (Memo<Option<T>>, WriteSignal<Option<T>>)) -> Self {
Self::Option(read.into(), write, None)
}
}
impl<T: Send + Sync> From<(Memo<Vec<T>>, WriteSignal<Vec<T>>)> for VecModel<T> {
fn from((read, write): (Memo<Vec<T>>, WriteSignal<Vec<T>>)) -> Self {
Self::Vec(read.into(), write, None)
}
}
impl<T: Default + Send + Sync> From<(Option<T>, WriteSignal<T>)> for VecModel<T> {
fn from((read, write): (Option<T>, WriteSignal<T>)) -> Self {
let mut model = Self::new(read.unwrap_or_default());
if let VecModel::T(_, _, on_write) = &mut model {
*on_write = Some(write);
}
model
}
}
impl<T: Default + Send + Sync> From<(Option<Option<T>>, WriteSignal<Option<T>>)> for VecModel<T> {
fn from((read, write): (Option<Option<T>>, WriteSignal<Option<T>>)) -> Self {
let mut model = Self::new_option(read.unwrap_or_default());
if let VecModel::Option(_, _, on_write) = &mut model {
*on_write = Some(write);
}
model
}
}
impl<T: Default + Send + Sync> From<(Option<Vec<T>>, WriteSignal<Vec<T>>)> for VecModel<T> {
fn from((read, write): (Option<Vec<T>>, WriteSignal<Vec<T>>)) -> Self {
let mut model = Self::new_vec(read.unwrap_or_default());
if let VecModel::Vec(_, _, on_write) = &mut model {
*on_write = Some(write);
}
model
}
}