use std::ops::Deref;
use leptos::{html::ElementDescriptor, *};
use web_sys::HtmlInputElement;
use crate::{OptionalMaybeSignal, Out};
fn prepare_autofocus<T: ElementDescriptor + Clone + Deref<Target = HtmlInputElement> + 'static>(
node_ref: NodeRef<T>,
) {
node_ref.on_load(move |elem| {
let outcome = elem.focus();
if let Err(err) = outcome {
tracing::error!(?err, "Could not update autofocus.");
}
});
}
fn use_focus<T: ElementDescriptor + Clone + Deref<Target = HtmlInputElement> + 'static>(
focus: Signal<bool>,
node_ref: NodeRef<T>,
) {
create_effect(move |_prev| {
let focus = focus.get();
let elem = node_ref.get();
if let Some(elem) = elem {
let outcome = match focus {
true => elem.focus(),
false => elem.blur(),
};
if let Err(err) = outcome {
tracing::error!(?err, "Could not update focus to {}.", focus);
}
}
});
}
#[component]
pub fn TextInput(
#[prop(into)] get: MaybeSignal<String>,
#[prop(into, optional)] set: Option<Out<String>>,
#[prop(optional, into)] placeholder: OptionalMaybeSignal<String>,
#[prop(optional, into)] prepend: OptionalMaybeSignal<View>,
#[prop(into, optional)] id: Option<AttributeValue>,
#[prop(into, optional)] class: Option<AttributeValue>,
#[prop(into, optional)] disabled: OptionalMaybeSignal<bool>,
#[prop(into, optional)] should_be_focused: Option<Signal<bool>>,
#[prop(into, optional)] on_focus_change: Option<Callback<bool>>,
#[prop(into, optional)] autofocus: bool,
#[prop(into, optional)] style: Option<AttributeValue>,
) -> impl IntoView {
let node_ref: NodeRef<html::Input> = create_node_ref();
if autofocus {
prepare_autofocus(node_ref);
}
if let Some(focus) = should_be_focused {
use_focus(focus, node_ref);
}
view! {
<leptonic-input style=style>
<input
node_ref=node_ref
id=id
class=class
placeholder=move || match &placeholder.0 {
Some(label) => Oco::from(label.get()),
None => Oco::from(""),
}
type="text"
prop:disabled=move || disabled.0.as_ref().map_or(false, SignalGet::get)
prop:value=move || get.get()
on:change=move |e| { if let Some(set) = &set { set.set(event_target::<HtmlInputElement>(&e).value()) } }
on:keyup=move |e| { if let Some(set) = &set { set.set(event_target::<HtmlInputElement>(&e).value()) } }
on:blur=move |_e| { if let Some(cb) = &on_focus_change { cb.call(false) }; }
on:focus=move |_e| { if let Some(cb) = &on_focus_change { cb.call(true) }; }
/>
{match prepend.0 {
Some(view) => view! {
<div>
{ view.get() }
</div>
}.into_view(),
None => ().into_view(),
}}
</leptonic-input>
}
}
#[component]
pub fn PasswordInput(
#[prop(into)] get: MaybeSignal<String>,
#[prop(into, optional)] set: Option<Out<String>>,
#[prop(optional, into)] placeholder: OptionalMaybeSignal<String>,
#[prop(optional, into)] prepend: OptionalMaybeSignal<View>,
#[prop(into, optional)] id: Option<AttributeValue>,
#[prop(into, optional)] class: Option<AttributeValue>,
#[prop(into, optional)] disabled: OptionalMaybeSignal<bool>,
#[prop(into, optional)] should_be_focused: Option<Signal<bool>>,
#[prop(into, optional)] on_focus_change: Option<Callback<bool>>,
#[prop(into, optional)] autofocus: bool,
#[prop(into, optional)] style: Option<AttributeValue>,
) -> impl IntoView {
let node_ref: NodeRef<html::Input> = create_node_ref();
if autofocus {
prepare_autofocus(node_ref);
}
if let Some(focus) = should_be_focused {
use_focus(focus, node_ref);
}
view! {
<leptonic-input style=style>
<input
node_ref=node_ref
id=id
class=class
placeholder=move || match &placeholder.0 {
Some(label) => leptos::Oco::from(label.get()),
None => leptos::Oco::from(""),
}
type="password"
prop:disabled=move || disabled.0.as_ref().map_or(false, SignalGet::get)
prop:value=move || get.get()
on:change=move |e| { if let Some(set) = &set { set.set(event_target::<HtmlInputElement>(&e).value()) } }
on:keyup=move |e| { if let Some(set) = &set { set.set(event_target::<HtmlInputElement>(&e).value()) } }
on:blur=move |_e| { if let Some(cb) = &on_focus_change { cb.call(false) }; }
on:focus=move |_e| { if let Some(cb) = &on_focus_change { cb.call(true) }; }
/>
{match prepend.0 {
Some(view) => view! {
<div>
{ view.get() }
</div>
}.into_view(),
None => ().into_view(),
}}
</leptonic-input>
}
}
#[component]
pub fn NumberInput(
#[prop(into)] get: MaybeSignal<f64>,
#[prop(into, optional)] set: Option<Out<f64>>,
#[prop(optional)] min: Option<f64>,
#[prop(optional)] max: Option<f64>,
#[prop(optional)] step: Option<f64>,
#[prop(optional, into)] placeholder: OptionalMaybeSignal<String>,
#[prop(optional, into)] prepend: OptionalMaybeSignal<View>,
#[prop(into, optional)] id: Option<AttributeValue>,
#[prop(into, optional)] class: Option<AttributeValue>,
#[prop(into, optional)] disabled: OptionalMaybeSignal<bool>,
#[prop(into, optional)] should_be_focused: Option<Signal<bool>>,
#[prop(into, optional)] on_focus_change: Option<Callback<bool>>,
#[prop(into, optional)] autofocus: bool,
#[prop(into, optional)] style: Option<AttributeValue>,
) -> impl IntoView {
let node_ref: NodeRef<html::Input> = create_node_ref();
if autofocus {
prepare_autofocus(node_ref);
}
if let Some(focus) = should_be_focused {
use_focus(focus, node_ref);
}
let set_value = set.map(|set| {
Callback::new(move |v: String| {
let parsed = str::parse::<f64>(&v).ok();
if let Some(parsed) = parsed {
set.set(parsed);
}
})
});
view! {
<leptonic-input style=style>
<input
node_ref=node_ref
id=id
class=class
placeholder=move || match &placeholder.0 {
Some(label) => leptos::Oco::from(label.get()),
None => leptos::Oco::from(""),
}
type="number"
min=min
max=max
step=step
prop:disabled=move || disabled.0.as_ref().map(SignalGet::get).unwrap_or(false)
prop:value=move || get.get()
on:change=move |e| { if let Some(set_value) = &set_value { set_value.call(event_target::<HtmlInputElement>(&e).value()) } }
on:keyup=move |e| { if let Some(set_value) = &set_value { set_value.call(event_target::<HtmlInputElement>(&e).value()) } }
on:blur=move |_e| { if let Some(cb) = &on_focus_change { cb.call(false) }; }
on:focus=move |_e| { if let Some(cb) = &on_focus_change { cb.call(true) }; }
/>
{match prepend.0 {
Some(view) => view! {
<div>
{ view.get() }
</div>
}.into_view(),
None => ().into_view(),
}}
</leptonic-input>
}
}