orbital_base_components/form/slider/
base.rs1use leptos::{context::Provider, ev, prelude::*};
2
3use crate::form::bind::FormBind;
4use crate::form::field_injection::FieldInjection;
5
6#[derive(Clone, Copy)]
7pub(crate) struct SliderInjection {
8 pub max: Signal<f64>,
9 pub min: Signal<f64>,
10 pub vertical: Signal<bool>,
11}
12
13impl SliderInjection {
14 pub fn expect_context() -> Self {
15 expect_context()
16 }
17}
18
19#[component(transparent)]
21pub fn BaseSlider(
22 #[prop(optional, into)] class: MaybeProp<String>,
23 #[prop(optional, into)] style: MaybeProp<String>,
24 #[prop(optional, into)] id: MaybeProp<String>,
25 #[prop(optional, into)] name: MaybeProp<String>,
26 #[prop(optional, into)] value: FormBind<f64>,
27 #[prop(default = 0.0.into(), into)] min: Signal<f64>,
28 #[prop(default = 100.0.into(), into)] max: Signal<f64>,
29 #[prop(optional, into)] step: MaybeProp<f64>,
30 #[prop(default = true.into(), into)] show_stops: Signal<bool>,
31 #[prop(optional, into)] vertical: Signal<bool>,
32 #[prop(optional)] children: Option<Children>,
33) -> impl IntoView {
34 let (id, name) = FieldInjection::use_id_and_name(id, name);
35 let has_labels = children.is_some();
36
37 let current_value_value = value.clone();
38 let current_value = Memo::new(move |_| {
39 let max = max.get();
40 let min = min.get();
41 let v = current_value_value.get();
42 if v > max {
43 max
44 } else if v < min {
45 min
46 } else {
47 v
48 }
49 });
50
51 let on_input_value = value.clone();
52 let on_input = move |e: ev::Event| {
53 if let Ok(range_value) = event_target_value(&e).parse::<f64>() {
54 on_input_value.set(range_value);
55 }
56 };
57
58 let css_vars = move || {
59 let max = max.get();
60 let min = min.get();
61 let mut css_vars = format!(
62 "--orbital-slider--progress: {:.2}%;",
63 if max == min {
64 0.0
65 } else {
66 (current_value.get() - min) / (max - min) * 100.0
67 }
68 );
69
70 if vertical.get() {
71 css_vars.push_str("--orbital-slider--direction: 0deg;");
72 } else {
73 css_vars.push_str("--orbital-slider--direction: 90deg;");
74 }
75
76 if has_labels {
77 css_vars.push_str(&format!("--orbital-slider--max: {:.2};", max));
78 css_vars.push_str(&format!("--orbital-slider--min: {:.2};", min));
79 }
80
81 if let Some(step) = step.get() {
82 if step > 0.0 && show_stops.get() && max != min {
83 css_vars.push_str(&format!(
84 "--orbital-slider--steps-percent: {:.2}%;",
85 step * 100.0 / (max - min)
86 ));
87 }
88 }
89
90 if let Some(extra_style) = style.get() {
91 css_vars.push_str(&extra_style);
92 }
93
94 css_vars
95 };
96
97 view! {
98 <div class=move || {
99 let mut parts = vec![
100 "orbital-slider".to_string(),
101 if vertical.get() {
102 "orbital-slider--vertical".to_string()
103 } else {
104 "orbital-slider--horizontal".to_string()
105 },
106 ];
107 if let Some(extra) = class.get() {
108 if !extra.is_empty() {
109 parts.push(extra);
110 }
111 }
112 parts.join(" ")
113 } style=css_vars>
114 <input
115 min=move || min.get()
116 max=move || max.get()
117 step=move || step.get()
118 type="range"
119 class="orbital-slider__input"
120 id=id
121 name=name
122 on:input=on_input
123 value=current_value.get_untracked()
124 prop:value=move || current_value.get()
125 />
126 <div class="orbital-slider__rail"></div>
127 <div class="orbital-slider__thumb"></div>
128 {if let Some(children) = children {
129 let slider = SliderInjection { max, min, vertical };
130 view! {
131 <Provider value=slider>
132 <div class="orbital-slider__datalist">{children()}</div>
133 </Provider>
134 }
135 .into_any()
136 } else {
137 ().into_any()
138 }}
139 </div>
140 }
141}