euv_ui/component/slider/view/struct.rs
1use super::*;
2
3/// The multiplier converting a `0.0..=1.0` ratio into a `0.0..=100.0`
4/// percentage.
5const PERCENT_SCALE: f64 = 100.0;
6
7/// Props for the `euv_slider` component.
8///
9/// Defines the strongly-typed interface for a range slider. The value is
10/// owned by the caller through a `Signal<f64>` so the slider can take
11/// part in two-way binding with the rest of the page.
12#[derive(Clone, CustomDebug, Data, Default)]
13pub struct EuvSliderProps {
14 /// The unique identifier for the range input element.
15 #[get(type(copy))]
16 pub id: &'static str,
17 /// The HTML name attribute for the range input element.
18 #[get(type(copy))]
19 pub name: &'static str,
20 /// The lowest selectable value.
21 #[get(type(copy))]
22 pub min: f64,
23 /// The highest selectable value.
24 #[get(type(copy))]
25 pub max: f64,
26 /// The granularity of the selectable values.
27 #[get(type(copy))]
28 pub step: f64,
29 /// The signal bound to the current slider value.
30 #[get(type(copy))]
31 pub value: Signal<f64>,
32 /// The label text displayed above the slider track.
33 #[get(type(copy))]
34 pub label: &'static str,
35 /// Optional input event handler; the built-in handler is used when
36 /// this is `None`.
37 #[debug(skip)]
38 pub oninput: Option<Rc<dyn Fn(Event)>>,
39}
40
41/// Stateless helper functions backing the `euv_slider` component.
42///
43/// The slider needs two operations that are pure functions of their
44/// arguments — percentage mapping and the value-writing input handler.
45/// Grouping them here keeps the `#[component]` body free of arithmetic
46/// and event plumbing.
47pub struct EuvSliderHelpers;
48
49impl EuvSliderHelpers {
50 /// Maps `value` onto the `0.0..=100.0` percentage range spanned by
51 /// `min..=max`.
52 ///
53 /// A degenerate range (`min == max`) reports `0.0` instead of
54 /// dividing by zero, and values outside the range are clamped so a
55 /// mis-typed signal can never produce a nonsensical percentage.
56 ///
57 /// # Arguments
58 ///
59 /// - `f64` - The value to map.
60 /// - `f64` - The low end of the range.
61 /// - `f64` - The high end of the range.
62 ///
63 /// # Returns
64 ///
65 /// - `f64` - The percentage position of `value` within the range.
66 pub fn percent(value: f64, min: f64, max: f64) -> f64 {
67 if max <= min {
68 return 0.0;
69 }
70 let ratio: f64 = (value - min) / (max - min);
71 let clamped: f64 = ratio.clamp(0.0, 1.0);
72 clamped * PERCENT_SCALE
73 }
74
75 /// Builds an input handler that writes the range value into `signal`.
76 ///
77 /// The parsed value is clamped to the `[min, max]` window so a
78 /// keyboard-driven overshoot cannot push the signal outside the
79 /// range the component advertises.
80 ///
81 /// # Arguments
82 ///
83 /// - `Signal<f64>` - The signal updated with the new value.
84 /// - `f64` - The low end of the accepted range.
85 /// - `f64` - The high end of the accepted range.
86 ///
87 /// # Returns
88 ///
89 /// - `Option<Rc<dyn Fn(Event)>>` - An input handler writing the
90 /// clamped range value into the signal.
91 pub fn on_slider_input(signal: Signal<f64>, min: f64, max: f64) -> Option<Rc<dyn Fn(Event)>> {
92 Some(Rc::new(move |event: Event| {
93 let raw: Option<f64> = event.target().and_then(|target: EventTarget| {
94 if let Ok(input) = target.dyn_into::<HtmlInputElement>() {
95 return input.value().parse::<f64>().ok();
96 }
97 None
98 });
99 if let Some(parsed) = raw {
100 signal.set(parsed.clamp(min, max));
101 }
102 }))
103 }
104}