use crate::util::{Size, TestAttr};
use leptos::callback::{Callable, Callback};
use leptos::prelude::Effect;
use leptos::prelude::event_target_value;
use leptos::prelude::{
Children, ClassAttribute, CustomAttribute, ElementChild, Get, GetUntracked, IntoView, NodeRef,
NodeRefAttribute, OnAttribute, PropAttribute, Signal, component, view,
};
use leptos::wasm_bindgen::JsCast;
use leptos::web_sys::{HtmlOptionElement, HtmlSelectElement};
fn event_target_values(ev: &leptos::ev::Event) -> Vec<String> {
let target = ev.target().expect("event should have a target");
let select = target.unchecked_into::<HtmlSelectElement>();
let options = select.selected_options();
let len = options.length();
let mut values = Vec::with_capacity(len as usize);
for i in 0..len {
let option = options.item(i).expect("should have item");
let option = option.unchecked_into::<HtmlOptionElement>();
values.push(option.value());
}
values
}
fn size_class(size: Size) -> &'static str {
match size {
Size::Large => "is-large",
Size::Medium => "is-medium",
Size::Normal => "is-normal",
Size::Small => "is-small",
}
}
#[component]
pub fn Select(
#[prop(into)]
name: Signal<String>,
#[prop(into)]
value: Signal<String>,
update: Callback<String>,
children: Children,
#[prop(optional, into)]
classes: Signal<String>,
#[prop(optional)]
size: Option<Size>,
#[prop(optional, into)]
loading: Signal<bool>,
#[prop(optional, into)]
disabled: Signal<bool>,
#[prop(optional, into)]
test_attr: Option<TestAttr>,
) -> impl IntoView {
let mut wrapper_classes = vec!["select".to_string()];
let extra = classes.get_untracked();
if !extra.trim().is_empty() {
wrapper_classes.push(extra);
}
if let Some(sz) = size {
wrapper_classes.push(size_class(sz).to_string());
}
if loading.get_untracked() {
wrapper_classes.push("is-loading".to_string());
}
let wrapper_class = wrapper_classes.join(" ");
let name_value = name.get_untracked();
let _initial_value = value.get_untracked();
let is_disabled = disabled.get_untracked();
let (data_testid, data_cy) = match &test_attr {
Some(attr) if attr.key == "data-testid" => (Some(attr.value.clone()), None),
Some(attr) if attr.key == "data-cy" => (None, Some(attr.value.clone())),
_ => (None, None),
};
view! {
<div
class=wrapper_class
attr:data-testid=data_testid
attr:data-cy=data_cy
>
<select
name=name_value
disabled=is_disabled
on:change=move |v| update.run(event_target_value(&v))
prop:value=value
>
{children()}
</select>
</div>
}
}
#[component]
pub fn MultiSelect(
#[prop(into)]
name: Signal<String>,
#[prop(into)]
value: Signal<Vec<String>>,
update: Callback<Vec<String>>,
children: Children,
#[prop(optional, into)]
classes: Signal<String>,
#[prop(optional)]
size: Option<Size>,
#[prop(optional)]
list_size: Option<u32>,
#[prop(optional, into)]
loading: Signal<bool>,
#[prop(optional, into)]
disabled: Signal<bool>,
#[prop(optional, into)]
test_attr: Option<TestAttr>,
) -> impl IntoView {
let mut wrapper_classes = vec!["select".to_string(), "is-multiple".to_string()];
let extra = classes.get_untracked();
if !extra.trim().is_empty() {
wrapper_classes.push(extra);
}
if let Some(sz) = size {
wrapper_classes.push(size_class(sz).to_string());
}
if loading.get_untracked() {
wrapper_classes.push("is-loading".to_string());
}
let wrapper_class = wrapper_classes.join(" ");
let name_value = name.get_untracked();
let is_disabled = disabled.get_untracked();
let size_attr = list_size.unwrap_or(4).to_string();
let _initial_values = value.get_untracked();
let (data_testid, data_cy) = match &test_attr {
Some(attr) if attr.key == "data-testid" => (Some(attr.value.clone()), None),
Some(attr) if attr.key == "data-cy" => (None, Some(attr.value.clone())),
_ => (None, None),
};
let select_ref = NodeRef::<leptos::html::Select>::new();
Effect::new(move |_| {
if let Some(select) = select_ref.get() {
let values = value.get();
let options = select.get_elements_by_tag_name("option");
for i in 0..options.length() {
let option = options
.item(i)
.unwrap()
.unchecked_into::<HtmlOptionElement>();
option.set_selected(values.contains(&option.value()));
}
}
});
view! {
<div
class=wrapper_class
attr:data-testid=data_testid
attr:data-cy=data_cy
>
<select
node_ref=select_ref
multiple=true
size=size_attr
name=name_value
disabled=is_disabled
on:change=move |v| update.run(event_target_values(&v))
>
{children()}
</select>
</div>
}
}