refactor: Callback

This commit is contained in:
luoxiao 2024-07-19 17:30:46 +08:00
parent 769f69c069
commit c186c8b352
26 changed files with 241 additions and 117 deletions

View file

@ -19,7 +19,7 @@ pub fn SiteHeader() -> impl IntoView {
}) })
}); });
// let (_, write_theme, _) = use_local_storage::<String, FromToStringCodec>("theme"); // let (_, write_theme, _) = use_local_storage::<String, FromToStringCodec>("theme");
let change_theme = Callback::new(move |_| { let change_theme = move |_| {
if theme_name.get_untracked() == "Light" { if theme_name.get_untracked() == "Light" {
theme.set(Theme::light()); theme.set(Theme::light());
// write_theme.set("light".to_string()); // write_theme.set("light".to_string());
@ -27,7 +27,7 @@ pub fn SiteHeader() -> impl IntoView {
theme.set(Theme::dark()); theme.set(Theme::dark());
// write_theme.set("dark".to_string()); // write_theme.set("dark".to_string());
} }
}); };
let search_value = RwSignal::new(String::new()); let search_value = RwSignal::new(String::new());
let search_all_options = StoredValue::new(gen_search_all_options()); let search_all_options = StoredValue::new(gen_search_all_options());
@ -186,7 +186,7 @@ pub fn SiteHeader() -> impl IntoView {
<Space class="demo-header__right-btn" align=SpaceAlign::Center> <Space class="demo-header__right-btn" align=SpaceAlign::Center>
<Button <Button
appearance=ButtonAppearance::Subtle appearance=ButtonAppearance::Subtle
on_click=Callback::new(move |_| change_theme.call(())) on_click=change_theme
> >
{move || theme_name.get()} {move || theme_name.get()}
</Button> </Button>

View file

@ -4,19 +4,19 @@
let open = RwSignal::new(false); let open = RwSignal::new(false);
let position = RwSignal::new(DrawerPosition::Top); let position = RwSignal::new(DrawerPosition::Top);
let open_f = Callback::new(move |new_position: DrawerPosition| { let open_f = move |new_position: DrawerPosition| {
// Note: Since `show` changes are made in real time, // Note: Since `show` changes are made in real time,
// please put it in front of `show.set(true)` when changing `placement`. // please put it in front of `show.set(true)` when changing `placement`.
position.set(new_position); position.set(new_position);
open.set(true); open.set(true);
}); };
view! { view! {
<ButtonGroup> <ButtonGroup>
<Button on_click=Callback::new(move |_| open_f.call(DrawerPosition::Top))>"Top"</Button> <Button on_click=move |_| open_f(DrawerPosition::Top)>"Top"</Button>
<Button on_click=Callback::new(move |_| open_f.call(DrawerPosition::Right))>"Right"</Button> <Button on_click=move |_| open_f(DrawerPosition::Right)>"Right"</Button>
<Button on_click=Callback::new(move |_| open_f.call(DrawerPosition::Bottom))>"Bottom"</Button> <Button on_click=move |_| open_f(DrawerPosition::Bottom)>"Bottom"</Button>
<Button on_click=Callback::new(move |_| open_f.call(DrawerPosition::Left))>"Left"</Button> <Button on_click=move |_| open_f(DrawerPosition::Left)>"Left"</Button>
</ButtonGroup> </ButtonGroup>
<OverlayDrawer open position> <OverlayDrawer open position>
<DrawerHeader> <DrawerHeader>

View file

@ -70,13 +70,13 @@ view! {
let value = RwSignal::new(String::from("o")); let value = RwSignal::new(String::from("o"));
let input_ref = ComponentRef::<InputRef>::new(); let input_ref = ComponentRef::<InputRef>::new();
let focus = Callback::new(move |_| { let focus = move |_| {
input_ref.get_untracked().unwrap().focus() input_ref.get_untracked().unwrap().focus()
}); };
let blur = Callback::new(move |_| { let blur = move |_| {
input_ref.get_untracked().unwrap().blur() input_ref.get_untracked().unwrap().blur()
}); };
view! { view! {
<Space vertical=true> <Space vertical=true>

View file

@ -9,9 +9,8 @@ view! {
### Closable ### Closable
```rust demo ```rust demo
use send_wrapper::SendWrapper;
// let message = use_message(); // let message = use_message();
let success = move |_: SendWrapper<ev::MouseEvent>| { let success = move |_: ev::MouseEvent| {
// message.create( // message.create(
// "tag close".into(), // "tag close".into(),
// MessageVariant::Success, // MessageVariant::Success,

View file

@ -24,10 +24,9 @@ view!{
### Drag to upload ### Drag to upload
```rust demo ```rust demo
use send_wrapper::SendWrapper;
// let message = use_message(); // let message = use_message();
let custom_request = move |file_list: SendWrapper<FileList>| { let custom_request = move |file_list: FileList| {
// message.create( // message.create(
// format!("Number of uploaded files: {}", file_list.length()), // format!("Number of uploaded files: {}", file_list.length()),
// MessageVariant::Success, // MessageVariant::Success,

View file

@ -6,7 +6,8 @@ use thaw_utils::{mount_style, update, with, StoredMaybeSignal};
#[component] #[component]
pub fn AccordionItem( pub fn AccordionItem(
/// Required value that identifies this item inside an Accordion component. /// Required value that identifies this item inside an Accordion component.
#[prop(into)] value: MaybeSignal<String>, #[prop(into)]
value: MaybeSignal<String>,
accordion_header: AccordionHeader, accordion_header: AccordionHeader,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
@ -57,10 +58,10 @@ pub fn AccordionItem(
aria-hidden="true" aria-hidden="true"
width="1em" width="1em"
height="1em" height="1em"
viewBox="0 0 20 20" viewBox="0 0 20 20"
style=move || if is_show_panel.get() { style=move || if is_show_panel.get() {
"transform: rotate(90deg)" "transform: rotate(90deg)"
} else { } else {
"transform: rotate(0deg)" "transform: rotate(0deg)"
} }
> >

View file

@ -5,7 +5,7 @@ pub use auto_complete_option::AutoCompleteOption;
use crate::{ComponentRef, ConfigInjection, Input, InputPrefix, InputRef, InputSuffix}; use crate::{ComponentRef, ConfigInjection, Input, InputPrefix, InputRef, InputSuffix};
use leptos::{context::Provider, either::Either, html, prelude::*}; use leptos::{context::Provider, either::Either, html, prelude::*};
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth}; use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth};
use thaw_utils::{class_list, mount_style, Model, OptionalProp}; use thaw_utils::{class_list, mount_style, BoxOneCallback, Model, OptionalProp};
#[slot] #[slot]
pub struct AutoCompletePrefix { pub struct AutoCompletePrefix {
@ -23,7 +23,7 @@ pub fn AutoComplete(
#[prop(optional, into)] placeholder: OptionalProp<MaybeSignal<String>>, #[prop(optional, into)] placeholder: OptionalProp<MaybeSignal<String>>,
#[prop(optional, into)] clear_after_select: MaybeSignal<bool>, #[prop(optional, into)] clear_after_select: MaybeSignal<bool>,
#[prop(optional, into)] blur_after_select: MaybeSignal<bool>, #[prop(optional, into)] blur_after_select: MaybeSignal<bool>,
#[prop(optional, into)] on_select: Option<Callback<String>>, #[prop(optional, into)] on_select: Option<BoxOneCallback<String>>,
#[prop(optional, into)] disabled: MaybeSignal<bool>, #[prop(optional, into)] disabled: MaybeSignal<bool>,
#[prop(optional, into)] allow_free_input: bool, #[prop(optional, into)] allow_free_input: bool,
#[prop(optional, into)] invalid: MaybeSignal<bool>, #[prop(optional, into)] invalid: MaybeSignal<bool>,
@ -60,8 +60,8 @@ pub fn AutoComplete(
} else { } else {
value.set(option_value.clone()); value.set(option_value.clone());
} }
if let Some(on_select) = on_select { if let Some(on_select) = on_select.as_ref() {
on_select.call(option_value); on_select(option_value);
} }
if allow_free_input { if allow_free_input {
select_option_index.set(None); select_option_index.set(None);

View file

@ -2,8 +2,8 @@ use crate::{ConfigInjection, Icon};
use leptos::{either::Either, ev, html, prelude::*}; use leptos::{either::Either, ev, html, prelude::*};
use thaw_components::{CSSTransition, Teleport}; use thaw_components::{CSSTransition, Teleport};
use thaw_utils::{ use thaw_utils::{
add_event_listener, class_list, get_scroll_parent, mount_style, EventListenerHandle, add_event_listener, class_list, get_scroll_parent, mount_style, BoxCallback,
OptionalProp, EventListenerHandle, OptionalProp,
}; };
#[component] #[component]
@ -28,7 +28,7 @@ pub fn BackTop(
} }
}); });
let scroll_to_top = StoredValue::new(None::<Callback<()>>); let scroll_to_top = StoredValue::new(None::<BoxCallback>);
let scroll_handle = StoredValue::new(None::<EventListenerHandle>); let scroll_handle = StoredValue::new(None::<EventListenerHandle>);
Effect::new(move |_| { Effect::new(move |_| {
@ -42,7 +42,7 @@ pub fn BackTop(
{ {
let scroll_el = send_wrapper::SendWrapper::new(scroll_el.clone()); let scroll_el = send_wrapper::SendWrapper::new(scroll_el.clone());
scroll_to_top.set_value(Some(Callback::new(move |_| { scroll_to_top.set_value(Some(BoxCallback::new(move || {
scroll_el.scroll_to_with_scroll_to_options( scroll_el.scroll_to_with_scroll_to_options(
web_sys::ScrollToOptions::new() web_sys::ScrollToOptions::new()
.top(0.0) .top(0.0)
@ -69,7 +69,7 @@ pub fn BackTop(
let on_click = move |_| { let on_click = move |_| {
scroll_to_top.with_value(|scroll_to_top| { scroll_to_top.with_value(|scroll_to_top| {
if let Some(scroll_to_top) = scroll_to_top { if let Some(scroll_to_top) = scroll_to_top {
scroll_to_top.call(()); scroll_to_top();
} }
}); });
}; };

View file

@ -4,8 +4,7 @@ pub use button_group::ButtonGroup;
use crate::icon::Icon; use crate::icon::Icon;
use leptos::{either::Either, ev, prelude::*}; use leptos::{either::Either, ev, prelude::*};
use send_wrapper::SendWrapper; use thaw_utils::{class_list, mount_style, BoxOneCallback, OptionalMaybeSignal, OptionalProp};
use thaw_utils::{class_list, mount_style, OptionalMaybeSignal, OptionalProp};
#[derive(Default, PartialEq, Clone, Copy)] #[derive(Default, PartialEq, Clone, Copy)]
pub enum ButtonAppearance { pub enum ButtonAppearance {
@ -73,7 +72,7 @@ pub fn Button(
#[prop(optional, into)] icon: OptionalMaybeSignal<icondata_core::Icon>, #[prop(optional, into)] icon: OptionalMaybeSignal<icondata_core::Icon>,
#[prop(optional, into)] disabled: MaybeSignal<bool>, #[prop(optional, into)] disabled: MaybeSignal<bool>,
#[prop(optional, into)] disabled_focusable: MaybeSignal<bool>, #[prop(optional, into)] disabled_focusable: MaybeSignal<bool>,
#[prop(optional, into)] on_click: Option<Callback<SendWrapper<ev::MouseEvent>>>, #[prop(optional, into)] on_click: Option<BoxOneCallback<ev::MouseEvent>>,
#[prop(optional)] children: Option<Children>, #[prop(optional)] children: Option<Children>,
) -> impl IntoView { ) -> impl IntoView {
mount_style("button", include_str!("./button.css")); mount_style("button", include_str!("./button.css"));
@ -87,10 +86,10 @@ pub fn Button(
return; return;
} }
let Some(callback) = on_click.as_ref() else { let Some(on_click) = on_click.as_ref() else {
return; return;
}; };
callback.call(SendWrapper::new(e)); on_click(e);
}; };
view! { view! {

View file

@ -34,7 +34,7 @@ pub fn DatePicker(
show_date_text.set(text); show_date_text.set(text);
}); });
let on_input_blur = Callback::new(move |_| { let on_input_blur = move |_| {
if let Ok(date) = if let Ok(date) =
NaiveDate::parse_from_str(&show_date_text.get_untracked(), show_date_format) NaiveDate::parse_from_str(&show_date_text.get_untracked(), show_date_format)
{ {
@ -45,7 +45,7 @@ pub fn DatePicker(
} else { } else {
update_show_date_text(); update_show_date_text();
} }
}); };
let close_panel = Callback::new(move |date: Option<NaiveDate>| { let close_panel = Callback::new(move |date: Option<NaiveDate>| {
if value.get_untracked() != date { if value.get_untracked() != date {
@ -57,13 +57,13 @@ pub fn DatePicker(
is_show_panel.set(false); is_show_panel.set(false);
}); });
let open_panel = Callback::new(move |_| { let open_panel = move |_| {
panel_selected_date.set(value.get_untracked()); panel_selected_date.set(value.get_untracked());
if let Some(panel_ref) = panel_ref.get_untracked() { if let Some(panel_ref) = panel_ref.get_untracked() {
panel_ref.init_panel(value.get_untracked().unwrap_or(now_date())); panel_ref.init_panel(value.get_untracked().unwrap_or(now_date()));
} }
is_show_panel.set(true); is_show_panel.set(true);
}); };
view! { view! {
<Binder target_ref=date_picker_ref> <Binder target_ref=date_picker_ref>

View file

@ -80,9 +80,9 @@ pub fn DatePanel(
*date = *date + Months::new(1); *date = *date + Months::new(1);
}); });
}; };
let now = Callback::new(move |_| { let now = move |_| {
close_panel.call(Some(now_date())); close_panel.call(Some(now_date()));
}); };
view! { view! {
<div> <div>
<div class="thaw-date-picker-date-panel__calendar"> <div class="thaw-date-picker-date-panel__calendar">

View file

@ -5,7 +5,7 @@ mod year_panel;
use crate::ConfigInjection; use crate::ConfigInjection;
use chrono::NaiveDate; use chrono::NaiveDate;
use date_panel::DatePanel; use date_panel::DatePanel;
use leptos::{ev, html, prelude::*}; use leptos::{html, prelude::*};
use month_panel::MonthPanel; use month_panel::MonthPanel;
use thaw_components::CSSTransition; use thaw_components::CSSTransition;
use thaw_utils::{now_date, ComponentRef}; use thaw_utils::{now_date, ComponentRef};
@ -24,7 +24,7 @@ pub fn Panel(
#[cfg(any(feature = "csr", feature = "hydrate"))] #[cfg(any(feature = "csr", feature = "hydrate"))]
{ {
use leptos::wasm_bindgen::__rt::IntoJsResult; use leptos::wasm_bindgen::__rt::IntoJsResult;
let handle = window_event_listener(ev::click, move |ev| { let handle = window_event_listener(leptos::ev::click, move |ev| {
let el = ev.target(); let el = ev.target();
let mut el: Option<web_sys::Element> = let mut el: Option<web_sys::Element> =
el.into_js_result().map_or(None, |el| Some(el.into())); el.into_js_result().map_or(None, |el| Some(el.into()));

View file

@ -19,9 +19,9 @@ pub fn Dialog(
open.set(false); open.set(false);
} }
}; };
let on_esc = Callback::new(move |_: ev::KeyboardEvent| { let on_esc = move |_: ev::KeyboardEvent| {
open.set(false); open.set(false);
}); };
view! { view! {
<Teleport immediate=open.signal()> <Teleport immediate=open.signal()>

View file

@ -28,7 +28,7 @@ pub fn OverlayDrawer(
open_drawer.set(is_open); open_drawer.set(is_open);
}); });
use_lock_html_scroll(is_lock.into()); use_lock_html_scroll(is_lock.into());
let on_after_leave = move |_| { let on_after_leave = move || {
is_lock.set(false); is_lock.set(false);
}; };
@ -38,9 +38,9 @@ pub fn OverlayDrawer(
open.set(false); open.set(false);
} }
}; };
let on_esc = Callback::new(move |_: ev::KeyboardEvent| { let on_esc = move |_: ev::KeyboardEvent| {
open.set(false); open.set(false);
}); };
view! { view! {
<Teleport immediate=open.signal()> <Teleport immediate=open.signal()>

View file

@ -1,7 +1,7 @@
// copy https://github.com/Carlosted/leptos-icons // copy https://github.com/Carlosted/leptos-icons
// leptos updated version causes leptos_icons error // leptos updated version causes leptos_icons error
use leptos::{ev, prelude::*}; use leptos::{ev, prelude::*};
use thaw_utils::{class_list, mount_style}; use thaw_utils::{class_list, mount_style, BoxOneCallback};
/// The Icon component. /// The Icon component.
#[component] #[component]
@ -23,7 +23,7 @@ pub fn Icon(
style: Option<MaybeSignal<String>>, style: Option<MaybeSignal<String>>,
/// Callback when clicking on the icon. /// Callback when clicking on the icon.
#[prop(optional, into)] #[prop(optional, into)]
on_click: Option<Callback<ev::MouseEvent>>, on_click: Option<BoxOneCallback<ev::MouseEvent>>,
) -> impl IntoView { ) -> impl IntoView {
mount_style("icon", include_str!("./icon.css")); mount_style("icon", include_str!("./icon.css"));
@ -41,7 +41,7 @@ pub fn Icon(
let icon_data = RwSignal::new(None); let icon_data = RwSignal::new(None);
let on_click = move |ev| { let on_click = move |ev| {
if let Some(click) = on_click.as_ref() { if let Some(click) = on_click.as_ref() {
click.call(ev); click(ev);
} }
}; };

View file

@ -1,6 +1,5 @@
use leptos::{ev, html, prelude::*}; use leptos::{ev, html, prelude::*};
use send_wrapper::SendWrapper; use thaw_utils::{class_list, mount_style, BoxOneCallback, ComponentRef, Model, OptionalProp};
use thaw_utils::{class_list, mount_style, ComponentRef, Model, OptionalProp};
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub enum InputVariant { pub enum InputVariant {
@ -35,11 +34,11 @@ pub struct InputSuffix {
#[component] #[component]
pub fn Input( pub fn Input(
#[prop(optional, into)] value: Model<String>, #[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] allow_value: Option<Callback<String, bool>>, #[prop(optional, into)] allow_value: Option<BoxOneCallback<String, bool>>,
#[prop(optional, into)] variant: MaybeSignal<InputVariant>, #[prop(optional, into)] variant: MaybeSignal<InputVariant>,
#[prop(optional, into)] placeholder: OptionalProp<MaybeSignal<String>>, #[prop(optional, into)] placeholder: OptionalProp<MaybeSignal<String>>,
#[prop(optional, into)] on_focus: Option<Callback<SendWrapper<ev::FocusEvent>>>, #[prop(optional, into)] on_focus: Option<BoxOneCallback<ev::FocusEvent>>,
#[prop(optional, into)] on_blur: Option<Callback<SendWrapper<ev::FocusEvent>>>, #[prop(optional, into)] on_blur: Option<BoxOneCallback<ev::FocusEvent>>,
#[prop(optional, into)] disabled: MaybeSignal<bool>, #[prop(optional, into)] disabled: MaybeSignal<bool>,
#[prop(optional, into)] invalid: MaybeSignal<bool>, #[prop(optional, into)] invalid: MaybeSignal<bool>,
#[prop(optional)] input_prefix: Option<InputPrefix>, #[prop(optional)] input_prefix: Option<InputPrefix>,
@ -56,7 +55,7 @@ pub fn Input(
move |ev| { move |ev| {
let input_value = event_target_value(&ev); let input_value = event_target_value(&ev);
if let Some(allow_value) = allow_value.as_ref() { if let Some(allow_value) = allow_value.as_ref() {
if !allow_value.call(input_value.clone()) { if !allow_value(input_value.clone()) {
value_trigger.trigger(); value_trigger.trigger();
return; return;
} }
@ -68,13 +67,13 @@ pub fn Input(
let on_internal_focus = move |ev| { let on_internal_focus = move |ev| {
is_focus.set(true); is_focus.set(true);
if let Some(on_focus) = on_focus.as_ref() { if let Some(on_focus) = on_focus.as_ref() {
on_focus.call(SendWrapper::new(ev)); on_focus(ev);
} }
}; };
let on_internal_blur = move |ev| { let on_internal_blur = move |ev| {
is_focus.set(false); is_focus.set(false);
if let Some(on_blur) = on_blur.as_ref() { if let Some(on_blur) = on_blur.as_ref() {
on_blur.call(SendWrapper::new(ev)); on_blur(ev);
} }
}; };

View file

@ -1,23 +1,15 @@
use leptos::{either::Either, ev, prelude::*}; use leptos::{either::Either, ev, prelude::*};
use send_wrapper::SendWrapper; use thaw_utils::{class_list, mount_style, ArcOneCallback, OptionalProp};
use thaw_utils::{class_list, mount_style, OptionalProp};
#[component] #[component]
pub fn Tag( pub fn Tag(
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>, #[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
#[prop(optional, into)] closable: MaybeSignal<bool>, #[prop(optional, into)] closable: MaybeSignal<bool>,
#[prop(optional, into)] on_close: Option<Callback<SendWrapper<ev::MouseEvent>>>, #[prop(optional, into)] on_close: Option<ArcOneCallback<ev::MouseEvent>>,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
mount_style("tag", include_str!("./tag.css")); mount_style("tag", include_str!("./tag.css"));
let on_close = move |event| {
let Some(callback) = on_close.as_ref() else {
return;
};
callback.call(SendWrapper::new(event));
};
view! { view! {
<span <span
class=class_list!["thaw-tag", ("thaw-tag--closable", move || closable.get()), class.map(| c | move || c.get())] class=class_list!["thaw-tag", ("thaw-tag--closable", move || closable.get()), class.map(| c | move || c.get())]
@ -26,6 +18,13 @@ pub fn Tag(
<span class="thaw-tag__primary-text">{children()}</span> <span class="thaw-tag__primary-text">{children()}</span>
{move || { {move || {
let on_close = on_close.clone();
let on_close = move |event| {
let Some(on_close) = on_close.as_ref() else {
return;
};
on_close(event);
};
if closable.get() { if closable.get() {
Either::Left(view! { Either::Left(view! {
<button class="thaw-tag__close" on:click=on_close> <button class="thaw-tag__close" on:click=on_close>

View file

@ -1,13 +1,13 @@
use leptos::{ev, html, prelude::*}; use leptos::{ev, html, prelude::*};
use thaw_utils::{class_list, mount_style, ComponentRef, Model}; use thaw_utils::{class_list, mount_style, BoxOneCallback, ComponentRef, Model};
#[component] #[component]
pub fn Textarea( pub fn Textarea(
#[prop(optional, into)] value: Model<String>, #[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] allow_value: Option<Callback<String, bool>>, #[prop(optional, into)] allow_value: Option<BoxOneCallback<String, bool>>,
#[prop(optional, into)] placeholder: MaybeProp<String>, #[prop(optional, into)] placeholder: MaybeProp<String>,
#[prop(optional, into)] on_focus: Option<Callback<ev::FocusEvent>>, #[prop(optional, into)] on_focus: Option<BoxOneCallback<ev::FocusEvent>>,
#[prop(optional, into)] on_blur: Option<Callback<ev::FocusEvent>>, #[prop(optional, into)] on_blur: Option<BoxOneCallback<ev::FocusEvent>>,
#[prop(optional, into)] disabled: MaybeSignal<bool>, #[prop(optional, into)] disabled: MaybeSignal<bool>,
/// Which direction the Textarea is allowed to be resized. /// Which direction the Textarea is allowed to be resized.
#[prop(optional, into)] #[prop(optional, into)]
@ -24,7 +24,7 @@ pub fn Textarea(
move |ev| { move |ev| {
let input_value = event_target_value(&ev); let input_value = event_target_value(&ev);
if let Some(allow_value) = allow_value.as_ref() { if let Some(allow_value) = allow_value.as_ref() {
if !allow_value.call(input_value.clone()) { if !allow_value(input_value.clone()) {
value_trigger.trigger(); value_trigger.trigger();
return; return;
} }
@ -34,12 +34,12 @@ pub fn Textarea(
}; };
let on_internal_focus = move |ev| { let on_internal_focus = move |ev| {
if let Some(on_focus) = on_focus.as_ref() { if let Some(on_focus) = on_focus.as_ref() {
on_focus.call(ev); on_focus(ev);
} }
}; };
let on_internal_blur = move |ev| { let on_internal_blur = move |ev| {
if let Some(on_blur) = on_blur.as_ref() { if let Some(on_blur) = on_blur.as_ref() {
on_blur.call(ev); on_blur(ev);
} }
}; };

View file

@ -3,7 +3,7 @@ use crate::{
SignalWatch, SignalWatch,
}; };
use chrono::{Local, NaiveTime, Timelike}; use chrono::{Local, NaiveTime, Timelike};
use leptos::{ev, html, prelude::*}; use leptos::{html, prelude::*};
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement}; use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement};
use thaw_utils::{mount_style, ComponentRef, Model, OptionalProp}; use thaw_utils::{mount_style, ComponentRef, Model, OptionalProp};
@ -36,7 +36,7 @@ pub fn TimePicker(
show_time_text.set(text); show_time_text.set(text);
}); });
let on_input_blur = Callback::new(move |_| { let on_input_blur = move |_| {
if let Ok(time) = if let Ok(time) =
NaiveTime::parse_from_str(&show_time_text.get_untracked(), show_time_format) NaiveTime::parse_from_str(&show_time_text.get_untracked(), show_time_format)
{ {
@ -47,7 +47,7 @@ pub fn TimePicker(
} else { } else {
update_show_time_text(); update_show_time_text();
} }
}); };
let close_panel = Callback::new(move |time: Option<NaiveTime>| { let close_panel = Callback::new(move |time: Option<NaiveTime>| {
if value.get_untracked() != time { if value.get_untracked() != time {
if time.is_some() { if time.is_some() {
@ -58,7 +58,7 @@ pub fn TimePicker(
is_show_panel.set(false); is_show_panel.set(false);
}); });
let open_panel = Callback::new(move |_| { let open_panel = move |_| {
panel_selected_time.set(value.get_untracked()); panel_selected_time.set(value.get_untracked());
is_show_panel.set(true); is_show_panel.set(true);
request_animation_frame(move || { request_animation_frame(move || {
@ -66,7 +66,7 @@ pub fn TimePicker(
panel_ref.scroll_into_view(); panel_ref.scroll_into_view();
} }
}); });
}); };
view! { view! {
<Binder target_ref=time_picker_ref> <Binder target_ref=time_picker_ref>
@ -99,18 +99,18 @@ fn Panel(
comp_ref: ComponentRef<PanelRef>, comp_ref: ComponentRef<PanelRef>,
) -> impl IntoView { ) -> impl IntoView {
let config_provider = ConfigInjection::use_(); let config_provider = ConfigInjection::use_();
let now = Callback::new(move |_| { let now = move |_| {
close_panel.call(Some(now_time())); close_panel.call(Some(now_time()));
}); };
let ok = Callback::new(move |_| { let ok = move |_| {
close_panel.call(selected_time.get_untracked()); close_panel.call(selected_time.get_untracked());
}); };
let panel_ref = NodeRef::<html::Div>::new(); let panel_ref = NodeRef::<html::Div>::new();
#[cfg(any(feature = "csr", feature = "hydrate"))] #[cfg(any(feature = "csr", feature = "hydrate"))]
{ {
use leptos::wasm_bindgen::__rt::IntoJsResult; use leptos::wasm_bindgen::__rt::IntoJsResult;
let handle = window_event_listener(ev::click, move |ev| { let handle = window_event_listener(leptos::ev::click, move |ev| {
let el = ev.target(); let el = ev.target();
let mut el: Option<web_sys::Element> = let mut el: Option<web_sys::Element> =
el.into_js_result().map_or(None, |el| Some(el.into())); el.into_js_result().map_or(None, |el| Some(el.into()));

View file

@ -190,13 +190,13 @@ fn ToasterContainer(
); );
} }
let on_before_leave = move |_| { let on_before_leave = move || {
let Some(el) = container_ref.get_untracked() else { let Some(el) = container_ref.get_untracked() else {
return; return;
}; };
el.style(("max-height", format!("{}px", el.offset_height()))); el.style(("max-height", format!("{}px", el.offset_height())));
}; };
let on_after_leave = move |_| { let on_after_leave = move || {
request_animation_frame(move || on_close.call((id, position))); request_animation_frame(move || on_close.call((id, position)));
}; };

View file

@ -4,14 +4,13 @@ pub use upload_dragger::UploadDragger;
pub use web_sys::FileList; pub use web_sys::FileList;
use leptos::{ev, html, prelude::*}; use leptos::{ev, html, prelude::*};
use send_wrapper::SendWrapper; use thaw_utils::{add_event_listener, mount_style, ArcOneCallback};
use thaw_utils::{add_event_listener, mount_style};
#[component] #[component]
pub fn Upload( pub fn Upload(
#[prop(optional, into)] accept: MaybeSignal<String>, #[prop(optional, into)] accept: MaybeSignal<String>,
#[prop(optional, into)] multiple: MaybeSignal<bool>, #[prop(optional, into)] multiple: MaybeSignal<bool>,
#[prop(optional, into)] custom_request: Option<Callback<SendWrapper<FileList>>>, #[prop(optional, into)] custom_request: Option<ArcOneCallback<FileList>>,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
mount_style("upload", include_str!("./upload.css")); mount_style("upload", include_str!("./upload.css"));
@ -34,17 +33,20 @@ pub fn Upload(
}); });
let on_file_addition = move |files: FileList| { let on_file_addition = move |files: FileList| {
if let Some(custom_request) = custom_request { if let Some(custom_request) = custom_request.as_ref() {
custom_request.call(SendWrapper::new(files)); custom_request(files);
} }
}; };
let on_change = move |_| { let on_change = {
if let Some(input_ref) = input_ref.get_untracked() { let on_file_addition = on_file_addition.clone();
if let Some(files) = input_ref.files() { move |_| {
on_file_addition(files); if let Some(input_ref) = input_ref.get_untracked() {
if let Some(files) = input_ref.files() {
on_file_addition(files);
}
input_ref.set_value("");
} }
input_ref.set_value("");
} }
}; };

View file

@ -1,6 +1,6 @@
use leptos::{ev, html::ElementType, prelude::*}; use leptos::{ev, html::ElementType, prelude::*};
use std::{ops::Deref, time::Duration}; use std::{ops::Deref, time::Duration};
use thaw_utils::{add_event_listener, EventListenerHandle, NextFrame}; use thaw_utils::{add_event_listener, ArcCallback, EventListenerHandle, NextFrame};
use web_sys::wasm_bindgen::JsCast; use web_sys::wasm_bindgen::JsCast;
/// # CSS Transition /// # CSS Transition
@ -12,12 +12,12 @@ pub fn CSSTransition<E, CF, IV>(
#[prop(into)] show: MaybeSignal<bool>, #[prop(into)] show: MaybeSignal<bool>,
#[prop(into)] name: MaybeSignal<String>, #[prop(into)] name: MaybeSignal<String>,
#[prop(optional)] appear: bool, #[prop(optional)] appear: bool,
#[prop(optional, into)] on_before_enter: Option<Callback<()>>, #[prop(optional, into)] on_before_enter: Option<ArcCallback>,
#[prop(optional, into)] on_enter: Option<Callback<()>>, #[prop(optional, into)] on_enter: Option<ArcCallback>,
#[prop(optional, into)] on_after_enter: Option<Callback<()>>, #[prop(optional, into)] on_after_enter: Option<ArcCallback>,
#[prop(optional, into)] on_before_leave: Option<Callback<()>>, #[prop(optional, into)] on_before_leave: Option<ArcCallback>,
#[prop(optional, into)] on_leave: Option<Callback<()>>, #[prop(optional, into)] on_leave: Option<ArcCallback>,
#[prop(optional, into)] on_after_leave: Option<Callback<()>>, #[prop(optional, into)] on_after_leave: Option<ArcCallback>,
children: CF, children: CF,
) -> impl IntoView ) -> impl IntoView
where where
@ -114,6 +114,12 @@ where
}; };
let name = name.clone(); let name = name.clone();
let on_before_enter = on_before_enter.clone();
let on_enter = on_enter.clone();
let on_after_enter = on_after_enter.clone();
let on_before_leave = on_before_leave.clone();
let on_leave = on_leave.clone();
let on_after_leave = on_after_leave.clone();
let effect = RenderEffect::new(move |prev: Option<bool>| { let effect = RenderEffect::new(move |prev: Option<bool>| {
let show = show.get(); let show = show.get();
let prev = if let Some(prev) = prev { let prev = if let Some(prev) = prev {
@ -131,7 +137,7 @@ where
{ {
// on_enter // on_enter
if let Some(on_before_enter) = on_before_enter.as_ref() { if let Some(on_before_enter) = on_before_enter.as_ref() {
on_before_enter.call(()); on_before_enter();
} }
let enter_from = format!("{name}-enter-from"); let enter_from = format!("{name}-enter-from");
let enter_active = format!("{name}-enter-active"); let enter_active = format!("{name}-enter-active");
@ -142,6 +148,8 @@ where
let class_list = class_list.clone(); let class_list = class_list.clone();
let on_end = on_end.clone(); let on_end = on_end.clone();
let on_enter = on_enter.clone();
let on_after_enter = on_after_enter.clone();
next_frame.run(move || { next_frame.run(move || {
let _ = class_list.remove_1(&enter_from); let _ = class_list.remove_1(&enter_from);
let _ = class_list.add_1(&enter_to); let _ = class_list.add_1(&enter_to);
@ -150,13 +158,13 @@ where
let remove = Box::new(move || { let remove = Box::new(move || {
let _ = class_list.remove_2(&enter_active, &enter_to); let _ = class_list.remove_2(&enter_active, &enter_to);
if let Some(on_after_enter) = on_after_enter.as_ref() { if let Some(on_after_enter) = on_after_enter.as_ref() {
on_after_enter.call(()); on_after_enter();
} }
}); });
on_end(remove); on_end(remove);
if let Some(on_enter) = on_enter.as_ref() { if let Some(on_enter) = on_enter.as_ref() {
on_enter.call(()); on_enter();
} }
}); });
} }
@ -165,7 +173,7 @@ where
{ {
// on_leave // on_leave
if let Some(on_before_leave) = on_before_leave.as_ref() { if let Some(on_before_leave) = on_before_leave.as_ref() {
on_before_leave.call(()); on_before_leave();
} }
let leave_from = format!("{name}-leave-from"); let leave_from = format!("{name}-leave-from");
let leave_active = format!("{name}-leave-active"); let leave_active = format!("{name}-leave-active");
@ -174,9 +182,9 @@ where
let _ = class_list.add_2(&leave_from, &leave_active); let _ = class_list.add_2(&leave_from, &leave_active);
let class_list = class_list.clone(); let class_list = class_list.clone();
let on_after_leave = on_after_leave.clone();
let on_end = on_end.clone(); let on_end = on_end.clone();
let on_leave = on_leave.clone(); let on_leave = on_leave.clone();
let on_after_leave = on_after_leave.clone();
next_frame.run(move || { next_frame.run(move || {
let _ = class_list.remove_1(&leave_from); let _ = class_list.remove_1(&leave_from);
let _ = class_list.add_1(&leave_to); let _ = class_list.add_1(&leave_to);
@ -186,12 +194,12 @@ where
let _ = class_list.remove_2(&leave_active, &leave_to); let _ = class_list.remove_2(&leave_active, &leave_to);
display.set(Some("display: none;")); display.set(Some("display: none;"));
if let Some(on_after_leave) = on_after_leave.as_ref() { if let Some(on_after_leave) = on_after_leave.as_ref() {
on_after_leave.call(()); on_after_leave();
} }
}); });
on_end(remove); on_end(remove);
if let Some(on_leave) = on_leave { if let Some(on_leave) = on_leave {
on_leave.call(()); on_leave();
} }
}); });
} }

View file

@ -1,4 +1,5 @@
use leptos::{ev, prelude::*}; use leptos::{ev, prelude::*};
use thaw_utils::ArcOneCallback;
#[cfg(any(feature = "csr", feature = "hydrate"))] #[cfg(any(feature = "csr", feature = "hydrate"))]
thread_local! { thread_local! {
@ -9,7 +10,7 @@ thread_local! {
pub fn FocusTrap( pub fn FocusTrap(
disabled: bool, disabled: bool,
#[prop(into)] active: MaybeSignal<bool>, #[prop(into)] active: MaybeSignal<bool>,
#[prop(into)] on_esc: Callback<ev::KeyboardEvent>, #[prop(into)] on_esc: ArcOneCallback<ev::KeyboardEvent>,
children: Children, children: Children,
) -> impl IntoView { ) -> impl IntoView {
#[cfg(any(feature = "csr", feature = "hydrate"))] #[cfg(any(feature = "csr", feature = "hydrate"))]
@ -36,7 +37,7 @@ pub fn FocusTrap(
let handle = window_event_listener(ev::keydown, move |e| { let handle = window_event_listener(ev::keydown, move |e| {
if &e.code() == "Escape" { if &e.code() == "Escape" {
if is_current_active() { if is_current_active() {
on_esc.call(e); on_esc(e);
} }
} }
}); });

115
thaw_utils/src/callback.rs Normal file
View file

@ -0,0 +1,115 @@
use std::{ops::Deref, sync::Arc};
pub struct BoxCallback(Box<dyn Fn() + Send + Sync + 'static>);
impl BoxCallback {
pub fn new<F>(f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
Self(Box::new(f))
}
}
impl Deref for BoxCallback {
type Target = Box<dyn Fn() + Send + Sync + 'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<F> From<F> for BoxCallback
where
F: Fn() + Send + Sync + 'static,
{
fn from(value: F) -> Self {
Self::new(value)
}
}
pub struct BoxOneCallback<A, Return = ()>(Box<dyn Fn(A) -> Return + Send + Sync + 'static>);
impl<A, Return> BoxOneCallback<A, Return> {
pub fn new<F>(f: F) -> Self
where
F: Fn(A) -> Return + Send + Sync + 'static,
{
Self(Box::new(f))
}
}
impl<A, Return> Deref for BoxOneCallback<A, Return> {
type Target = Box<dyn Fn(A) -> Return + Send + Sync + 'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<F, A, Return> From<F> for BoxOneCallback<A, Return>
where
F: Fn(A) -> Return + Send + Sync + 'static,
{
fn from(value: F) -> Self {
Self::new(value)
}
}
#[derive(Clone)]
pub struct ArcCallback(Arc<dyn Fn() + Send + Sync + 'static>);
impl ArcCallback {
pub fn new<F>(f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
Self(Arc::new(f))
}
}
impl Deref for ArcCallback {
type Target = Arc<dyn Fn() + Send + Sync + 'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<F> From<F> for ArcCallback
where
F: Fn() + Send + Sync + 'static,
{
fn from(value: F) -> Self {
Self::new(value)
}
}
#[derive(Clone)]
pub struct ArcOneCallback<A>(Arc<dyn Fn(A) + Send + Sync + 'static>);
impl<A> ArcOneCallback<A> {
pub fn new<F>(f: F) -> Self
where
F: Fn(A) + Send + Sync + 'static,
{
Self(Arc::new(f))
}
}
impl<A> Deref for ArcOneCallback<A> {
type Target = Arc<dyn Fn(A) + Send + Sync + 'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<F, A> From<F> for ArcOneCallback<A>
where
F: Fn(A) + Send + Sync + 'static,
{
fn from(value: F) -> Self {
Self::new(value)
}
}

View file

@ -7,8 +7,10 @@ mod optional_prop;
mod signals; mod signals;
mod throttle; mod throttle;
mod time; mod time;
mod callback;
pub use dom::*; pub use dom::*;
pub use callback::*;
pub use event_listener::{add_event_listener, add_event_listener_with_bool, EventListenerHandle}; pub use event_listener::{add_event_listener, add_event_listener_with_bool, EventListenerHandle};
pub use hooks::{use_click_position, use_lock_html_scroll, NextFrame}; pub use hooks::{use_click_position, use_lock_html_scroll, NextFrame};
pub use optional_prop::OptionalProp; pub use optional_prop::OptionalProp;

View file

@ -1,8 +1,8 @@
use leptos::{leptos_dom::helpers::TimeoutHandle, prelude::*}; use leptos::{leptos_dom::helpers::TimeoutHandle, prelude::*};
use std::time::Duration; use std::{sync::Arc, time::Duration};
pub fn throttle(cb: impl Fn() + Send + Sync + 'static, duration: Duration) -> impl Fn() -> () { pub fn throttle(cb: impl Fn() + Send + Sync + 'static, duration: Duration) -> impl Fn() -> () {
let cb = Callback::new(move |_| cb()); let cb = Arc::new(cb);
let timeout_handle = StoredValue::new(None::<TimeoutHandle>); let timeout_handle = StoredValue::new(None::<TimeoutHandle>);
on_cleanup(move || { on_cleanup(move || {
timeout_handle.update_value(move |handle| { timeout_handle.update_value(move |handle| {
@ -19,7 +19,7 @@ pub fn throttle(cb: impl Fn() + Send + Sync + 'static, duration: Duration) -> im
let cb = cb.clone(); let cb = cb.clone();
let handle = set_timeout_with_handle( let handle = set_timeout_with_handle(
move || { move || {
cb.call(()); cb();
timeout_handle.update_value(move |handle| { timeout_handle.update_value(move |handle| {
*handle = None; *handle = None;
}); });