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

View file

@ -5,114 +5,106 @@ pub use slider_label::SliderLabel;
use leptos::*; use leptos::*;
use thaw_components::OptionComp; use thaw_components::OptionComp;
use thaw_utils::{class_list, mount_style, Model, OptionalProp}; use thaw_utils::{class_list, mount_style, Model, OptionalProp};
use web_sys::DomRect;
#[component] #[component]
pub fn Slider( pub fn Slider(
#[prop(optional, into)] value: Model<f64>, #[prop(optional, into)] value: Model<f64>,
#[prop(default = MaybeSignal::Static(100f64), into)] max: MaybeSignal<f64>, #[prop(default = 0f64.into(), into)] min: MaybeSignal<f64>,
#[prop(optional, into)] step: 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, into)] class: OptionalProp<MaybeSignal<String>>,
#[prop(optional)] children: Option<Children>, #[prop(optional)] children: Option<Children>,
) -> impl IntoView { ) -> impl IntoView {
mount_style("slider", include_str!("./slider.css")); mount_style("slider", include_str!("./slider.css"));
let percentage = create_memo(move |_| { let is_chldren = children.is_some();
if value.get() < 0.0 || max.get() <= 0.0 { let list_id = is_chldren.then(|| uuid::Uuid::new_v4().to_string());
0f64 let current_value = Memo::new(move |_| {
} else if value.get() >= max.get() { let max = max.get();
100f64 let min = min.get();
let v = value.get();
if v > max {
max
} else if v < min {
min
} else { } else {
value.get() / max.get() * 100.0 v
} }
}); });
let do_update_value = move |mut val| { let on_input = move |e: ev::Event| {
let step = step.get_untracked(); 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 { if step > 0.0 {
let result: f64 = val / step; css_vars.push_str(&format!(
if result.fract() != 0.0 { "--thaw-slider--steps-percent: {:.2}%",
let prev_multiple = (result.floor() * step).abs(); step * 100.0 / (max - min)
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;
} }
} }
} css_vars
value.set(val);
}; };
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! { view! {
<Provider value=SliderInjection { max, min }>
<div <div
class=class_list!["thaw-slider", class.map(| c | move || c.get())] class=class_list!["thaw-slider", class.map(| c | move || c.get())]
style=move || format!("--thaw-slider--direction: 90deg;--thaw-slider--progress: {:.2}%", value.get()) style=css_vars
on:click=on_mouse_click
> >
<input type="range" class="thaw-slider__input"/> <input
<div class="thaw-slider__rail" ref=rail_ref> 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>
<div class="thaw-slider__thumb"> <div class="thaw-slider__thumb">
</div> </div>
<OptionComp value=children let:children> <OptionComp value=children let:children>
<datalist id=list_id class="thaw-slider__datalist">
{children()} {children()}
</datalist>
</OptionComp> </OptionComp>
<div
on:mousedown=on_mouse_down
class="thaw-slider-handle"
style=move || { format!("left: {}%", percentage.get()) }
></div>
</div> </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); var(--colorNeutralStroke1);
} }
.thaw-slider-rail { .thaw-slider__datalist {
height: 4px; display: block;
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 {
position: absolute; position: absolute;
top: 0px; width: 100%;
width: 16px; top: calc(var(--thaw-slider__thumb--size) + 4px);
height: 16px;
border-radius: 50%;
background-color: white;
box-shadow: 0px 0px 4px #2224;
transform: translateX(-50%);
} }

View file

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

View file

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