use super::*;
const FALLBACK_STEP: i64 = 1;
const STEP_UP_GLYPH: &str = "+";
const STEP_DOWN_GLYPH: &str = "\u{2212}";
pub fn stepped_value(current: i64, step: i64, min: Option<i64>, max: Option<i64>) -> Option<i64> {
let step: i64 = if step == 0 { FALLBACK_STEP } else { step };
let candidate: i64 = current.saturating_add(step);
let blocked: bool = if step > 0 {
max.is_some_and(|max_value: i64| candidate > max_value)
} else {
min.is_some_and(|min_value: i64| candidate < min_value)
};
if blocked {
return None;
}
let lowered: i64 = match min {
Some(min_value) => candidate.max(min_value),
None => candidate,
};
Some(match max {
Some(max_value) => lowered.min(max_value),
None => lowered,
})
}
pub fn stepper_disabled(current: i64, step: i64, min: Option<i64>, max: Option<i64>) -> bool {
stepped_value(current, step, min, max).is_none()
}
fn on_stepper_click(
value: Signal<i64>,
step: i64,
min: Option<i64>,
max: Option<i64>,
on_change: Option<Rc<dyn Fn(i64)>>,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_event: Event| {
let current: i64 = value.get();
let Some(next) = stepped_value(current, step, min, max) else {
return;
};
value.set(next);
if let Some(callback) = &on_change {
callback(next);
}
}))
}
fn on_number_change(
value: Signal<i64>,
on_change: Option<Rc<dyn Fn(i64)>>,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |event: Event| {
let reported: Option<String> = event.target().and_then(|target: EventTarget| {
if let Ok(input) = target.dyn_into::<HtmlInputElement>() {
return Some(input.value());
}
None
});
let Some(reported) = reported else {
return;
};
let Ok(parsed) = reported.trim().parse::<i64>() else {
return;
};
value.set(parsed);
if let Some(callback) = &on_change {
callback(parsed);
}
}))
}
#[component]
pub fn euv_number_input(node: VirtualNode<EuvNumberInputProps>) -> VirtualNode {
let EuvNumberInputProps {
value,
min,
max,
step,
label: label_text,
disabled,
on_change,
}: EuvNumberInputProps = node.try_get_props().unwrap_or_default();
let step_size: i64 = if step == 0 { FALLBACK_STEP } else { step };
let increment: i64 = step_size;
let decrement: i64 = -step_size;
html! {
div {
class: c_euv_input_wrapper()
if !label_text.is_empty() {
label {
class: c_form_label()
label_text
}
}
div {
class: c_euv_number_input()
input {
type: "number"
value: value.get().to_string()
disabled: disabled
class: c_euv_number_input_field()
onchange: on_number_change(value, on_change.clone())
}
button {
type: "button"
class: if { stepper_disabled(value.get(), increment, min, max) || disabled } {
c_euv_number_input_stepper_disabled()
} else {
c_euv_number_input_stepper()
}
onclick: on_stepper_click(value, increment, min, max, on_change.clone())
STEP_UP_GLYPH
}
button {
type: "button"
class: if { stepper_disabled(value.get(), decrement, min, max) || disabled } {
c_euv_number_input_stepper_disabled()
} else {
c_euv_number_input_stepper()
}
onclick: on_stepper_click(value, decrement, min, max, on_change.clone())
STEP_DOWN_GLYPH
}
}
}
}
}