use crate::Decorated;
use crate::{IntoText, TextSource};
use day_core::{BuildCx, Flex, Piece, RNode, with_tree};
use day_reactive::{Signal, bind_seeded};
use day_spec::Event;
use std::cell::RefCell;
use std::rc::Rc;
pub use day_spec::controls::combobox::*;
pub struct ComboBox {
items: Signal<Vec<String>>,
text: Signal<String>,
placeholder: Option<TextSource>,
}
pub fn combo_box(items: Signal<Vec<String>>, text: Signal<String>) -> ComboBox {
ComboBox {
items,
text,
placeholder: None,
}
}
#[crate::builder(ComboBoxBuilder)]
impl ComboBox {
pub fn placeholder<M>(mut self, t: impl IntoText<M>) -> Self {
self.placeholder = Some(t.into_text());
self
}
}
impl Piece for ComboBox {
fn build(self, cx: &mut BuildCx) -> RNode {
let ComboBox {
items,
text,
placeholder,
} = self;
let initial_items = items.get_untracked();
let initial_text = text.get_untracked();
let ph = placeholder.map(|p| p.initial()).unwrap_or_default();
let node = cx.leaf(
KIND,
&ComboProps {
items: initial_items.clone(),
text: initial_text.clone(),
placeholder: ph,
},
Flex {
grow_w: true,
..Default::default()
},
);
bind_seeded(
initial_items,
move || items.get(),
move |v: &Vec<String>| {
with_tree(|t| t.patch(node, Box::new(ComboPatch::Items(v.clone())), true));
},
);
let guard: Rc<RefCell<Option<String>>> = Rc::new(RefCell::new(None));
let g = guard.clone();
bind_seeded(
initial_text,
move || text.get(),
move |t: &String| {
let from_native = g.borrow_mut().take().as_deref() == Some(t.as_str());
if !from_native {
with_tree(|tr| tr.patch(node, Box::new(ComboPatch::SetText(t.clone())), false));
}
},
);
cx.on(node, move |ev| match ev {
Event::TextChanged(t) => {
*guard.borrow_mut() = Some(t.clone());
text.set(t.clone());
}
Event::SelectionChanged(i) => {
if *i >= 0
&& let Some(t) = items.get_untracked().get(*i as usize)
{
text.set(t.clone());
}
}
_ => {}
});
node
}
}
#[cfg(feature = "conformance")]
pub mod conformance {
use crate::*;
use day_core::conformance::{Case, Drive};
use day_reactive::Signal;
use super::{KIND, combo_box};
fn page() -> impl day_core::Piece {
let items = Signal::new(vec![
"Vanilla".to_string(),
"Chocolate".into(),
"Pistachio".into(),
]);
let text = Signal::new(String::new());
column((
label(move || format!("chose {}", text.get())).id(day_core::id!(combobox::chose)),
combo_box(items, text)
.placeholder("Flavor")
.id(day_core::id!(combobox::combo)),
))
}
#[day_macros::test(day_core)]
fn combobox_menu_selects() -> Case {
Case::new()
.proves(KIND)
.page(page)
.drive(|d: Drive| async move {
d.select("combobox::combo", 2).await?;
d.assert_text("combobox::chose", "chose Pistachio").await
})
}
#[day_macros::test(day_core)]
fn combobox_free_text() -> Case {
Case::new()
.proves(KIND)
.page(page)
.drive(|d: Drive| async move {
d.input("combobox::combo", "Stracciatella").await?;
d.assert_text("combobox::chose", "chose Stracciatella")
.await
})
}
}
pub fn support() -> day_spec::Support {
day_core::capability(day_spec::Cap::ComboBox)
}