mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
feat: adds VecModel
This commit is contained in:
parent
a653960040
commit
f11efb4ef8
5 changed files with 343 additions and 33 deletions
|
@ -1,7 +1,7 @@
|
|||
# Combobox
|
||||
|
||||
```rust demo
|
||||
let selected_options = RwSignal::new(vec![]);
|
||||
let selected_options = RwSignal::new(None::<String>);
|
||||
|
||||
view! {
|
||||
<Combobox selected_options>
|
||||
|
@ -17,7 +17,7 @@ view! {
|
|||
let selected_options = RwSignal::new(vec![]);
|
||||
|
||||
view! {
|
||||
<Combobox selected_options multiselect=true clearable=true>
|
||||
<Combobox selected_options clearable=true>
|
||||
<ComboboxOption value="cat" text="Car" />
|
||||
<ComboboxOption value="dog" text="Dog" />
|
||||
</Combobox>
|
||||
|
@ -30,7 +30,7 @@ view! {
|
|||
let selected_options = RwSignal::new(vec![]);
|
||||
|
||||
view! {
|
||||
<Combobox selected_options multiselect=true>
|
||||
<Combobox selected_options>
|
||||
<ComboboxOption value="cat" text="Car" />
|
||||
<ComboboxOption value="dog" text="Dog" />
|
||||
</Combobox>
|
||||
|
|
|
@ -3,13 +3,12 @@ use crate::_aria::use_active_descendant;
|
|||
use leptos::{context::Provider, ev, html, prelude::*};
|
||||
use std::collections::HashMap;
|
||||
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]
|
||||
pub fn Combobox(
|
||||
#[prop(optional, into)] value: Model<String>,
|
||||
#[prop(optional, into)] selected_options: Model<Vec<String>>,
|
||||
#[prop(optional)] multiselect: bool,
|
||||
#[prop(optional, into)] selected_options: VecModel<String>,
|
||||
#[prop(optional)] clearable: bool,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
|
@ -23,7 +22,12 @@ pub fn Combobox(
|
|||
let clear_icon_ref = NodeRef::<html::Span>::new();
|
||||
let is_show_clear_icon = Memo::new(move |_| {
|
||||
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 {
|
||||
false
|
||||
}
|
||||
|
@ -75,21 +79,24 @@ pub fn Combobox(
|
|||
|
||||
let on_input = move |ev| {
|
||||
let input_value = event_target_value(&ev);
|
||||
if !multiselect {
|
||||
if selected_options.with_untracked(|options| {
|
||||
if let Some(option) = options.first() {
|
||||
if option != &input_value {
|
||||
return true;
|
||||
}
|
||||
if selected_options.with_untracked(|options| match options {
|
||||
(None, None, Some(_)) => false,
|
||||
(None, Some(v), None) => {
|
||||
if let Some(v) = v.as_ref() {
|
||||
v != &input_value
|
||||
} else {
|
||||
false
|
||||
}
|
||||
false
|
||||
}) {
|
||||
selected_options.set(vec![]);
|
||||
}
|
||||
(Some(v), None, None) => v != &input_value,
|
||||
_ => unreachable!(),
|
||||
}) {
|
||||
selected_options.set(vec![]);
|
||||
}
|
||||
value.set(input_value);
|
||||
};
|
||||
|
||||
let multiselect = selected_options.is_vec();
|
||||
let combobox_injection = ComboboxInjection {
|
||||
value,
|
||||
multiselect,
|
||||
|
@ -103,13 +110,20 @@ pub fn Combobox(
|
|||
let on_blur = {
|
||||
let active_descendant_controller = active_descendant_controller.clone();
|
||||
move |_| {
|
||||
if multiselect {
|
||||
value.set(String::new());
|
||||
} else {
|
||||
if selected_options.with_untracked(|options| options.is_empty()) {
|
||||
value.set(String::new());
|
||||
selected_options.with_untracked(|options| match options {
|
||||
(None, None, Some(_)) => value.set(String::new()),
|
||||
(None, Some(v), None) => {
|
||||
if v.is_none() {
|
||||
value.set(String::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(v), None, None) => {
|
||||
if v.is_empty() {
|
||||
value.set(String::new())
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
active_descendant_controller.blur();
|
||||
}
|
||||
};
|
||||
|
@ -208,7 +222,7 @@ pub fn Combobox(
|
|||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct ComboboxInjection {
|
||||
value: Model<String>,
|
||||
selected_options: Model<Vec<String>>,
|
||||
selected_options: VecModel<String>,
|
||||
options: StoredValue<HashMap<String, (String, String)>>,
|
||||
is_show_listbox: RwSignal<bool>,
|
||||
pub multiselect: bool,
|
||||
|
@ -229,23 +243,40 @@ impl ComboboxInjection {
|
|||
}
|
||||
|
||||
pub fn is_selected(&self, value: &String) -> bool {
|
||||
self.selected_options
|
||||
.with(|options| options.contains(value))
|
||||
self.selected_options.with(|options| match options {
|
||||
(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) {
|
||||
self.selected_options.update(|options| {
|
||||
if self.multiselect {
|
||||
if let Some(index) = options.iter().position(|v| v == value) {
|
||||
options.remove(index);
|
||||
self.selected_options.update(|options| match options {
|
||||
(None, None, Some(v)) => {
|
||||
if let Some(index) = v.iter().position(|v| v == value) {
|
||||
v.remove(index);
|
||||
return;
|
||||
}
|
||||
options.push(value.clone());
|
||||
} else {
|
||||
*options = vec![value.clone()];
|
||||
v.push(value.clone());
|
||||
}
|
||||
(None, Some(v), None) => {
|
||||
*v = Some(value.clone());
|
||||
self.value.set(text.clone());
|
||||
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!(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ mod signal_watch;
|
|||
mod stored_maybe_signal;
|
||||
|
||||
pub use component_ref::ComponentRef;
|
||||
pub use model::{Model, OptionModel};
|
||||
pub use model::{Model, OptionModel, VecModel};
|
||||
pub use optional_maybe_signal::OptionalMaybeSignal;
|
||||
pub use signal_watch::SignalWatch;
|
||||
pub use stored_maybe_signal::StoredMaybeSignal;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
mod option_model;
|
||||
mod vec_model;
|
||||
|
||||
pub use option_model::OptionModel;
|
||||
pub use vec_model::VecModel;
|
||||
|
||||
use leptos::reactive_graph::{
|
||||
computed::Memo,
|
||||
|
|
277
thaw_utils/src/signals/model/vec_model.rs
Normal file
277
thaw_utils/src/signals/model/vec_model.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue