use crate::components::input::validation::InputValueWrapper;
use crate::mixins::labelled_rect_mixin::labelled_rect_mixin;
use crate::prelude::ValidationResult;
use crate::theme::prelude::*;
use crate::utils::component_id;
use dominator::{clone, events, html, with_node, Dom};
use dwind::prelude::*;
use futures_signals::map_ref;
use futures_signals::signal::{and, not, or, Mutable, SignalExt};
use futures_signals_component_macro::component;
use web_sys::HtmlInputElement;
pub enum TextInputType {
Text,
Password,
}
#[component(render_fn=text_input)]
struct TextInput {
#[default(Box::new(Mutable::new("".to_string())))]
value: dyn InputValueWrapper + 'static,
#[signal]
#[default(ValidationResult::Valid)]
is_valid: ValidationResult,
#[signal]
#[default("".to_string())]
label: String,
#[default(Box::new(|| {}))]
on_submit: dyn (FnMut() -> ()) + 'static,
#[signal]
#[default(TextInputType::Text)]
input_type: TextInputType,
#[default(false)]
claim_focus: bool,
}
pub fn text_input(props: TextInputProps) -> Dom {
let TextInputProps {
value,
is_valid,
label,
mut on_submit,
input_type,
claim_focus,
apply,
} = props;
let label = label.broadcast();
let has_label = label.signal_ref(|v| v.len() > 0);
let has_value = value.value_signal_cloned().map(|v| v.len() > 0);
let is_focused = Mutable::new(false);
let parsed_validation_result = Mutable::new(ValidationResult::Valid);
let validation_signal = map_ref! {
let parse_result = parsed_validation_result.signal_cloned(),
let external_result = is_valid => {
if !parse_result.is_valid() {
parse_result.clone()
} else if !external_result.is_valid() {
external_result.clone()
} else {
ValidationResult::Valid
}
}
}
.broadcast();
let is_valid = validation_signal
.signal_ref(|validation| validation.is_valid())
.broadcast();
let raise_label = and(
has_label,
or(or(is_focused.signal(), has_value), not(is_valid.signal())),
);
let input_id = component_id("text-input");
let error_id = format!("{}-error", input_id);
html!("div", {
.dwclass!("grid")
.children([
html!("input" => HtmlInputElement, {
.attr("id", &input_id)
.dwclass!("text-base transition-all")
.dwclass!("dwui-bg-void-900 is(.light *):dwui-bg-void-300 text-base")
.dwclass!("p-l-2 rounded-t-sm")
.dwclass!("grid-col-1 grid-row-1")
.dwclass!("dwui-text-on-primary-300 is(.light *):dwui-text-on-primary-900")
.dwclass_signal!("h-10", is_valid.signal())
.dwclass_signal!("h-6", not(is_valid.signal()))
.style("outline", "none")
.attr_signal("aria-invalid", is_valid.signal().map(|valid| if valid { None } else { Some("true") }))
.attr_signal("aria-describedby", is_valid.signal().map(clone!(error_id => move |valid| {
if valid {
None
} else {
Some(error_id.clone())
}
})))
.focused(claim_focus)
.attr_signal("type", input_type.map(|t| {
match t {
TextInputType::Text => { "text" }
TextInputType::Password => { "password" }
}
}))
.with_node!(element => {
.future(value.value_signal_cloned().for_each(clone!(element => move |v| {
element.set_value(&v);
async move {}
})))
.event(clone!(parsed_validation_result => move |_: events::Input| {
let result = value.set(element.value());
if !result.is_valid() {
parsed_validation_result.set(result);
}
}))
})
.event(clone!(is_focused => move |_: events::FocusOut| {
is_focused.set(false);
}))
.event(clone!(is_focused => move |_: events::Focus| {
is_focused.set(true);
}))
.event(move |event: events::KeyDown| {
if event.key() == "Enter" {
on_submit()
}
})
}),
])
.apply(labelled_rect_mixin(label.signal_cloned(), raise_label, validation_signal.signal_cloned(), input_id))
.apply_if(apply.is_some(),|b| b.apply(apply.unwrap()))
})
}