use crate::{
core::{
assets,
attrs::{availability_attributes, component_attributes},
measure::{custom_property, CustomProperty},
},
prelude::*,
theme::use_stylesheets,
};
const BEM: Bem = Bem::new("fc-slider");
#[must_use]
pub fn slider_percent(value: f64, min: f64, max: f64) -> Percent {
let span = max - min;
if span > 0.0 {
Percent::new((value - min) / span * 100.0)
} else {
Percent::new(0.0)
}
}
#[component]
pub fn Slider(
label: AccessibleName,
#[props(default = 0.0)] min: f64,
#[props(default = 100.0)] max: f64,
#[props(default = 1.0)] step: f64,
#[props(default)] value: Option<ReadSignal<f64>>,
#[props(default)] default_value: f64,
#[props(default)] oninput: Option<EventHandler<f64>>,
#[props(default)]
show_value: bool,
#[props(default)]
marks: Vec<String>,
#[props(default)] invalid: bool,
#[props(default)] availability: Availability,
#[props(default)] class: Option<String>,
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
) -> Element {
let state = use_controllable(value, || default_value, oninput);
let id = use_id();
let current = state.get();
let attributes = component_attributes(
attributes,
ClassList::new(BEM.block()).extend(class.as_deref()),
&[],
);
let mut input = availability_attributes(&availability);
input.push(custom_property(
CustomProperty::SliderPct,
format!("{}%", slider_percent(current, min, max)),
));
rsx! {
if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_SLIDER_CSS } }
div { ..attributes,
input { class: BEM.element("input"), r#type: "range", id: "{id}", min: "{min}", max: "{max}", step: "{step}",
value: "{current}", aria_label: label.to_string(), aria_invalid: invalid.then_some("true"),
oninput: move |event| if let Ok(value) = event.value().parse::<f64>() { state.set(value); },
..input,
}
if show_value { output { class: BEM.element("value"), ..crate::spread![Attribute::new("for", id.to_string(), None, false)], "{current}" } }
if !marks.is_empty() {
ol { class: BEM.element("marks"), aria_hidden: "true",
for mark in marks { li { "{mark}" } }
}
}
}
}
}