refactor: Slider

This commit is contained in:
luoxiao 2024-05-31 17:12:32 +08:00
parent add2a0bae3
commit f5c0d2a522
5 changed files with 105 additions and 123 deletions

View file

@ -1,7 +1,7 @@
# Slider
```rust demo
let value = create_rw_signal(0.0);
let value = RwSignal::new(0.0);
view! {
<Slider value/>
@ -11,20 +11,20 @@ view! {
### Step
```rust demo
let value = create_rw_signal(0.0);
let value = RwSignal::new(0.0);
view! {
<Slider step=10.0 value/>
<Slider step=25.0 value/>
}
```
## Slider Label
```rust demo
let value = create_rw_signal(0.0);
let value = RwSignal::new(0.0);
view! {
<Slider value max=10.0 step=1.0>
<Slider value max=10.0 step=5.0>
<SliderLabel value=0.0>
"0"
</SliderLabel>

View file

@ -5,114 +5,106 @@ pub use slider_label::SliderLabel;
use leptos::*;
use thaw_components::OptionComp;
use thaw_utils::{class_list, mount_style, Model, OptionalProp};
use web_sys::DomRect;
#[component]
pub fn Slider(
#[prop(optional, into)] value: Model<f64>,
#[prop(default = MaybeSignal::Static(100f64), into)] max: MaybeSignal<f64>,
#[prop(optional, into)] step: MaybeSignal<f64>,
#[prop(default = 0f64.into(), into)] min: MaybeSignal<f64>,
#[prop(default = 100f64.into(), into)] max: MaybeSignal<f64>,
#[prop(optional, into)] step: MaybeProp<f64>,
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
#[prop(optional)] children: Option<Children>,
) -> impl IntoView {
mount_style("slider", include_str!("./slider.css"));
let percentage = create_memo(move |_| {
if value.get() < 0.0 || max.get() <= 0.0 {
0f64
} else if value.get() >= max.get() {
100f64
let is_chldren = children.is_some();
let list_id = is_chldren.then(|| uuid::Uuid::new_v4().to_string());
let current_value = Memo::new(move |_| {
let max = max.get();
let min = min.get();
let v = value.get();
if v > max {
max
} else if v < min {
min
} else {
value.get() / max.get() * 100.0
v
}
});
let do_update_value = move |mut val| {
let step = step.get_untracked();
if step > 0.0 {
let result: f64 = val / step;
if result.fract() != 0.0 {
let prev_multiple = (result.floor() * step).abs();
let mut next_multiple = (result.ceil() * step).abs();
let max = max.get_untracked();
if next_multiple > max {
next_multiple = max;
}
if val - prev_multiple > next_multiple - val {
val = next_multiple;
} else {
val = prev_multiple;
}
let on_input = move |e: ev::Event| {
if let Ok(range_value) = event_target_value(&e).parse::<f64>() {
value.set(range_value);
}
};
let css_vars = move || {
let max = max.get();
let min = min.get();
let mut css_vars = format!(
"--thaw-slider--direction: 90deg;--thaw-slider--progress: {:.2}%;",
if max == min {
0.0
} else {
(current_value.get() - min) / (max - min) * 100.0
}
);
if is_chldren {
css_vars.push_str(&format!("--thaw-slider--max: {:.2};", max));
css_vars.push_str(&format!("--thaw-slider--min: {:.2};", min));
}
if let Some(step) = step.get() {
if step > 0.0 {
css_vars.push_str(&format!(
"--thaw-slider--steps-percent: {:.2}%",
step * 100.0 / (max - min)
));
}
}
value.set(val);
css_vars
};
let rail_ref = create_node_ref::<html::Div>();
let (mouse_move_value, set_mouse_move_value) = create_signal::<Option<f64>>(None);
let (is_mouse_move, set_mouse_move) = create_signal(false);
let on_mouse_down = move |_| {
set_mouse_move.set(true);
};
let check_value_and_update = move |ev_x: f64, rect: DomRect| {
if ev_x <= rect.x() {
set_mouse_move_value.set(Some(0.0));
} else if ev_x >= rect.x() + rect.width() {
set_mouse_move_value.set(Some(max.get()));
} else {
set_mouse_move_value.set(Some(((ev_x - rect.x()) / rect.width()) * max.get()));
}
if let Some(value) = mouse_move_value.get_untracked() {
do_update_value(value);
}
};
let on_mouse_click = move |ev: ev::MouseEvent| {
if let Some(rail) = rail_ref.get_untracked() {
let rect = rail.get_bounding_client_rect();
let ev_x = f64::from(ev.x());
check_value_and_update(ev_x, rect);
};
};
let on_mouse_up = window_event_listener(ev::mouseup, move |_| {
set_mouse_move.set(false);
});
on_cleanup(move || on_mouse_up.remove());
let on_mouse_move = window_event_listener(ev::mousemove, move |ev| {
if is_mouse_move.get_untracked() {
if let Some(rail) = rail_ref.get_untracked() {
let rect = rail.get_bounding_client_rect();
let ev_x = f64::from(ev.x());
check_value_and_update(ev_x, rect);
};
}
});
on_cleanup(move || on_mouse_move.remove());
view! {
<div
class=class_list!["thaw-slider", class.map(| c | move || c.get())]
style=move || format!("--thaw-slider--direction: 90deg;--thaw-slider--progress: {:.2}%", value.get())
on:click=on_mouse_click
>
<input type="range" class="thaw-slider__input"/>
<div class="thaw-slider__rail" ref=rail_ref>
</div>
<div class="thaw-slider__thumb">
</div>
<OptionComp value=children let:children>
{children()}
</OptionComp>
<Provider value=SliderInjection { max, min }>
<div
on:mousedown=on_mouse_down
class="thaw-slider-handle"
style=move || { format!("left: {}%", percentage.get()) }
></div>
</div>
class=class_list!["thaw-slider", class.map(| c | move || c.get())]
style=css_vars
>
<input
min=move || min.get()
max=move || max.get()
step=move || step.get()
type="range"
class="thaw-slider__input"
on:input=on_input
value=current_value.get_untracked()
prop:value=move || current_value.get()
list=list_id.clone()
/>
<div class="thaw-slider__rail">
</div>
<div class="thaw-slider__thumb">
</div>
<OptionComp value=children let:children>
<datalist id=list_id class="thaw-slider__datalist">
{children()}
</datalist>
</OptionComp>
</div>
</Provider>
}
}
#[derive(Clone)]
pub(crate) struct SliderInjection {
pub max: MaybeSignal<f64>,
pub min: MaybeSignal<f64>,
}
impl SliderInjection {
pub fn use_() -> Self {
expect_context()
}
}

View file

@ -125,26 +125,9 @@
var(--colorNeutralStroke1);
}
.thaw-slider-rail {
height: 4px;
background-color: var(--thaw-background-color);
border-radius: 2px;
cursor: pointer;
}
.thaw-slider-rail__fill {
width: 0%;
height: 4px;
background-color: var(--thaw-background-color-fill);
border-radius: 2px;
cursor: pointer;
}
.thaw-slider-handle {
.thaw-slider__datalist {
display: block;
position: absolute;
top: 0px;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: white;
box-shadow: 0px 0px 4px #2224;
transform: translateX(-50%);
width: 100%;
top: calc(var(--thaw-slider__thumb--size) + 4px);
}

View file

@ -2,5 +2,4 @@
position: absolute;
display: inline-block;
transform: translateX(-50%);
margin-top: 8px;
}

View file

@ -1,19 +1,27 @@
use leptos::*;
use thaw_utils::mount_style;
use crate::SliderInjection;
#[component]
pub fn SliderLabel(#[prop(into)] value: MaybeSignal<f64>, children: Children) -> impl IntoView {
mount_style("slider-label", include_str!("./slider_label.css"));
let slider = SliderInjection::use_();
let style = move || {
let value = (value.get() - slider.min.get()) / (slider.max.get() - slider.min.get());
format!("left: calc({} * (100% - var(--thaw-slider__thumb--size)) + var(--thaw-slider__thumb--size) / 2)", value)
};
view! {
<div
<option
class:thaw-slider-label=true
style=move || {
format!("left: calc(calc({} / var(--thaw-slider-max)) * 100%)", value.get())
}
style=style
value=move || value.get()
>
{children()}
</div>
</option>
}
}