use super::affix::{InputPrefix, InputSuffix};
use super::styles::input_styles;
use super::types::{InputAppearance, InputBind, InputEvents};
use leptos::{either::Either, ev, html, prelude::*, web_sys};
use orbital_base_components::{ComponentRef, FieldInjection, InputRef, InputRuleTrigger, Rule};
use orbital_macros::component_doc;
use orbital_style::inject_style;
#[component_doc(
category = "Inputs",
preview_slug = "input",
preview_label = "Input",
preview_icon = icondata::AiEditOutlined,
)]
#[component]
pub fn Input(
#[prop(optional, into)]
bind: InputBind,
#[prop(optional, into)]
appearance: InputAppearance,
#[prop(optional, into)]
events: InputEvents,
#[prop(optional, into)]
class: MaybeProp<String>,
#[prop(optional)]
comp_ref: ComponentRef<InputRef>,
#[prop(optional)]
input_prefix: Option<InputPrefix>,
#[prop(optional)]
input_suffix: Option<InputSuffix>,
) -> impl IntoView {
inject_style("orbital-input", input_styles());
let InputBind {
value,
id,
name,
rules,
} = bind;
let InputAppearance {
autofocus,
input_type,
placeholder,
disabled,
readonly,
input_size,
size,
autocomplete,
input_style,
parser,
format,
} = appearance;
let InputEvents {
on_focus,
on_blur,
allow_value,
} = events;
let (id, name) = FieldInjection::use_id_and_name(id, name);
let value = StoredValue::new(value);
let validate = Rule::validate(rules, value, name);
let parser_none = parser.is_none();
let on_input = {
let allow_value = allow_value.clone();
move |e| {
if !parser_none {
validate.run(Some(InputRuleTrigger::Input));
return;
}
let input_value = event_target_value(&e);
if let Some(allow_value) = allow_value.as_ref() {
if !allow_value(input_value.clone()) {
value.with_value(|v| v.update(|_| {}));
return;
}
}
value.with_value(|v| v.set(input_value));
validate.run(Some(InputRuleTrigger::Input));
}
};
let on_change = move |e| {
let Some(parser) = parser.as_ref() else {
validate.run(Some(InputRuleTrigger::Change));
return;
};
let Some(parsed_input_value) = parser(event_target_value(&e)) else {
value.with_value(|v| v.update(|_| {}));
return;
};
if let Some(allow_value) = allow_value.as_ref() {
if !allow_value(parsed_input_value.clone()) {
value.with_value(|v| v.update(|_| {}));
return;
}
}
value.with_value(|v| v.set(parsed_input_value));
validate.run(Some(InputRuleTrigger::Change));
};
let is_focus = RwSignal::new(false);
let on_internal_focus = move |ev: ev::FocusEvent| {
is_focus.set(true);
if let Some(on_focus) = on_focus.as_ref() {
on_focus(ev);
}
validate.run(Some(InputRuleTrigger::Focus));
};
let on_internal_blur = move |ev: ev::FocusEvent| {
is_focus.set(false);
if let Some(on_blur) = on_blur.as_ref() {
on_blur(ev);
}
validate.run(Some(InputRuleTrigger::Blur));
};
let prefix_if_ = input_prefix.as_ref().is_some_and(|prefix| prefix.if_);
let suffix_if_ = input_suffix.as_ref().is_some_and(|suffix| suffix.if_);
let size_class = Memo::new(move |_| format!("orbital-input--{}", size.get().as_str()));
let wrapper_class = Memo::new(move |_| {
let mut parts = vec!["orbital-input".to_string(), size_class.get()];
if prefix_if_ {
parts.push("orbital-input--prefix".to_string());
}
if suffix_if_ {
parts.push("orbital-input--suffix".to_string());
}
if disabled.get() {
parts.push("orbital-input--disabled".to_string());
}
if let Some(extra) = class.get() {
if !extra.is_empty() {
parts.push(extra);
}
}
parts.join(" ")
});
let input_ref = NodeRef::<html::Input>::new();
comp_ref.load(InputRef::new(input_ref));
let on_mousedown = move |event: ev::MouseEvent| {
let el: web_sys::HtmlElement = event_target(&event);
if el.tag_name() != "INPUT" {
event.prevent_default();
if !is_focus.get_untracked() {
if let Some(input_el) = input_ref.get_untracked() {
_ = input_el.focus();
}
}
}
};
view! {
<span class=wrapper_class on:mousedown=on_mousedown>
{if let Some(prefix) = input_prefix.and_then(|prefix| prefix.if_.then_some(prefix)) {
Either::Left(view! {
<div class="orbital-input__prefix">{(prefix.children)()}</div>
})
} else {
Either::Right(())
}}
<input
class="orbital-input__input"
id=id
type=move || input_type.get().as_str()
name=name
autofocus=autofocus
prop:value=move || {
let current = value.get_value().get();
if let Some(format) = format.as_ref() {
format(current)
} else {
current
}
}
on:input=on_input
on:change=on_change
on:focus=on_internal_focus
on:blur=on_internal_blur
disabled=disabled
readonly=readonly
size=input_size
placeholder=move || placeholder.get()
node_ref=input_ref
style=move || input_style.get()
autocomplete=move || autocomplete.get()
/>
{if let Some(suffix) = input_suffix.and_then(|suffix| suffix.if_.then_some(suffix)) {
Either::Left(view! {
<div class="orbital-input__suffix">{(suffix.children)()}</div>
})
} else {
Either::Right(())
}}
</span>
}
}