Skip to content

Commit af6fc3a

Browse files
ealmloffjkelleyrtp
andauthored
Switch to owned listener type (#4289)
* Switch to owned listener type * remove html event workaround * fix event conversion * keep track of the origin scope * fix clippy * fix docs * Use "ListenerCallback" instead of "ListenerCb" * Add a small example --------- Co-authored-by: Jonathan Kelley <[email protected]>
1 parent 3bf8f09 commit af6fc3a

File tree

5 files changed

+143
-270
lines changed

5 files changed

+143
-270
lines changed

packages/core-types/src/event.rs

Lines changed: 0 additions & 203 deletions
This file was deleted.

packages/core/src/events.rs

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
use crate::{properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId};
1+
use crate::{
2+
prelude::current_scope_id, properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId,
3+
};
24
use generational_box::GenerationalBox;
3-
use std::{cell::RefCell, marker::PhantomData, panic::Location, rc::Rc};
5+
use std::{any::Any, cell::RefCell, marker::PhantomData, panic::Location, rc::Rc};
46

57
/// A wrapper around some generic data that handles the event's state
68
///
@@ -67,6 +69,17 @@ impl<T: ?Sized> Event<T> {
6769
}
6870
}
6971

72+
/// Convert this event into a boxed event with a dynamic type
73+
pub fn into_any(self) -> Event<dyn Any>
74+
where
75+
T: Sized,
76+
{
77+
Event {
78+
data: self.data as Rc<dyn Any>,
79+
metadata: self.metadata,
80+
}
81+
}
82+
7083
/// Prevent this event from continuing to bubble up the tree to parent elements.
7184
///
7285
/// # Example
@@ -376,6 +389,27 @@ impl<
376389
}
377390
}
378391

392+
impl<
393+
Function: FnMut(Event<T>) -> Spawn + 'static,
394+
T: 'static,
395+
Spawn: SpawnIfAsync<Marker> + 'static,
396+
Marker,
397+
> SuperFrom<Function, MarkerWrapper<Marker>> for ListenerCallback<T>
398+
{
399+
fn super_from(input: Function) -> Self {
400+
ListenerCallback::new(input)
401+
}
402+
}
403+
404+
// ListenerCallback<T> can be created from Callback<Event<T>>
405+
impl<T: 'static> SuperFrom<Callback<Event<T>>> for ListenerCallback<T> {
406+
fn super_from(input: Callback<Event<T>>) -> Self {
407+
// https://github.com/rust-lang/rust-clippy/issues/15072
408+
#[allow(clippy::redundant_closure)]
409+
ListenerCallback::new(move |event| input(event))
410+
}
411+
}
412+
379413
#[doc(hidden)]
380414
pub struct UnitClosure<Marker>(PhantomData<Marker>);
381415

@@ -471,14 +505,6 @@ impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
471505
Self { callback, origin }
472506
}
473507

474-
/// Leak a new reference to the [`Callback`] that will not be dropped unless the callback is dropped manually
475-
pub(crate) fn leak_reference(&self) -> generational_box::BorrowResult<Callback<Args, Ret>> {
476-
Ok(Callback {
477-
callback: self.callback.leak_reference()?,
478-
origin: self.origin,
479-
})
480-
}
481-
482508
/// Call this callback with the appropriate argument type
483509
///
484510
/// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic.
@@ -559,3 +585,87 @@ impl<Args: 'static, Ret: 'static> std::ops::Deref for Callback<Args, Ret> {
559585
reference_to_closure as &_
560586
}
561587
}
588+
589+
type AnyEventHandler = Rc<RefCell<dyn FnMut(Event<dyn Any>)>>;
590+
591+
/// An owned callback type used in [`AttributeValue::Listener`](crate::AttributeValue::Listener).
592+
///
593+
/// This is the type that powers the `on` attributes in the `rsx!` macro, allowing you to pass event
594+
/// handlers to elements.
595+
///
596+
/// ```rust, ignore
597+
/// rsx! {
598+
/// button {
599+
/// onclick: AttributeValue::Listener(ListenerCallback::new(move |evt: Event<MouseData>| {
600+
/// // ...
601+
/// }
602+
/// }
603+
/// }
604+
/// ```
605+
pub struct ListenerCallback<T = ()> {
606+
pub(crate) origin: ScopeId,
607+
callback: AnyEventHandler,
608+
_marker: PhantomData<T>,
609+
}
610+
611+
impl<T> Clone for ListenerCallback<T> {
612+
fn clone(&self) -> Self {
613+
Self {
614+
origin: self.origin,
615+
callback: self.callback.clone(),
616+
_marker: PhantomData,
617+
}
618+
}
619+
}
620+
621+
impl<T> PartialEq for ListenerCallback<T> {
622+
fn eq(&self, other: &Self) -> bool {
623+
// We compare the pointers of the callbacks, since they are unique
624+
Rc::ptr_eq(&self.callback, &other.callback) && self.origin == other.origin
625+
}
626+
}
627+
628+
impl<T> ListenerCallback<T> {
629+
/// Create a new [`ListenerCallback`] from a callback
630+
///
631+
/// This is expected to be called within a runtime scope. Make sure a runtime is current before
632+
/// calling this method.
633+
pub fn new<MaybeAsync, Marker>(mut f: impl FnMut(Event<T>) -> MaybeAsync + 'static) -> Self
634+
where
635+
T: 'static,
636+
MaybeAsync: SpawnIfAsync<Marker>,
637+
{
638+
Self {
639+
origin: current_scope_id().expect("ListenerCallback must be created within a scope"),
640+
callback: Rc::new(RefCell::new(move |event: Event<dyn Any>| {
641+
let data = event.data.downcast::<T>().unwrap();
642+
f(Event {
643+
metadata: event.metadata.clone(),
644+
data,
645+
})
646+
.spawn();
647+
})),
648+
_marker: PhantomData,
649+
}
650+
}
651+
652+
/// Call the callback with an event
653+
///
654+
/// This is expected to be called within a runtime scope. Make sure a runtime is current before
655+
/// calling this method.
656+
pub fn call(&self, event: Event<dyn Any>) {
657+
let runtime = Runtime::current().expect("ListenerCallback must be called within a runtime");
658+
runtime.with_scope_on_stack(self.origin, || {
659+
(self.callback.borrow_mut())(event);
660+
});
661+
}
662+
663+
/// Erase the type of the callback, allowing it to be used with any type of event
664+
pub fn erase(self) -> ListenerCallback {
665+
ListenerCallback {
666+
origin: self.origin,
667+
callback: self.callback,
668+
_marker: PhantomData,
669+
}
670+
}
671+
}

packages/core/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ pub(crate) mod innerlude {
7979
pub use crate::innerlude::{
8080
fc_to_builder, generation, schedule_update, schedule_update_any, use_hook, vdom_is_rendering,
8181
AnyValue, Attribute, AttributeValue, CapturedError, Component, ComponentFunction, DynamicNode,
82-
Element, ElementId, Event, Fragment, HasAttributes, IntoDynNode, LaunchConfig, MarkerWrapper,
83-
Mutation, Mutations, NoOpMutations, Ok, Properties, Result, Runtime, ScopeId, ScopeState,
84-
SpawnIfAsync, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner,
85-
VPlaceholder, VText, VirtualDom, WriteMutations,
82+
Element, ElementId, Event, Fragment, HasAttributes, IntoDynNode, LaunchConfig,
83+
ListenerCallback, MarkerWrapper, Mutation, Mutations, NoOpMutations, Ok, Properties, Result,
84+
Runtime, ScopeId, ScopeState, SpawnIfAsync, Task, Template, TemplateAttribute, TemplateNode,
85+
VComponent, VNode, VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,
8686
};
8787

8888
/// The purpose of this module is to alleviate imports of many common types

0 commit comments

Comments
 (0)