dwui 0.7.4

UI Component library built on the DWIND style crate!
Documentation
use crate::components::input::validation::InputValueWrapper;
use crate::mixins::labelled_rect_mixin::labelled_rect_mixin;
use crate::prelude::ValidationResult;
use crate::theme::prelude::*;
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())),
    );

    html!("div", {
        .dwclass!("grid")
        .children([
            html!("input" => HtmlInputElement, {
                .dwclass!("text-base transition-all")
                .dwclass!("dwui-bg-void-900 is(.light *):dwui-bg-void-300 text-base")
                .dwclass!("p-l-2")
                .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()))
                .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()))
        .apply_if(apply.is_some(),|b| b.apply(apply.unwrap()))
    })
}