use leptos_reactive::Oco;
use std::marker::PhantomData;
use wasm_bindgen::convert::FromWasmAbi;
pub trait EventDescriptor: Clone {
type EventType: FromWasmAbi;
const BUBBLES: bool;
fn name(&self) -> Oco<'static, str>;
fn event_delegation_key(&self) -> Oco<'static, str>;
#[inline(always)]
fn options(&self) -> &Option<web_sys::AddEventListenerOptions> {
&None
}
}
#[derive(Clone, Debug)]
#[allow(non_camel_case_types)]
pub struct undelegated<Ev: EventDescriptor>(pub Ev);
impl<Ev: EventDescriptor> EventDescriptor for undelegated<Ev> {
type EventType = Ev::EventType;
#[inline(always)]
fn name(&self) -> Oco<'static, str> {
self.0.name()
}
#[inline(always)]
fn event_delegation_key(&self) -> Oco<'static, str> {
self.0.event_delegation_key()
}
const BUBBLES: bool = false;
}
#[derive(Debug)]
pub struct Custom<E: FromWasmAbi = web_sys::Event> {
name: Oco<'static, str>,
options: Option<web_sys::AddEventListenerOptions>,
_event_type: PhantomData<E>,
}
impl<E: FromWasmAbi> Clone for Custom<E> {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
options: self.options.clone(),
_event_type: PhantomData,
}
}
}
impl<E: FromWasmAbi> EventDescriptor for Custom<E> {
type EventType = E;
fn name(&self) -> Oco<'static, str> {
self.name.clone()
}
fn event_delegation_key(&self) -> Oco<'static, str> {
format!("$$${}", self.name).into()
}
const BUBBLES: bool = false;
#[inline(always)]
fn options(&self) -> &Option<web_sys::AddEventListenerOptions> {
&self.options
}
}
impl<E: FromWasmAbi> Custom<E> {
pub fn new(name: impl Into<Oco<'static, str>>) -> Self {
Self {
name: name.into(),
options: None,
_event_type: PhantomData,
}
}
pub fn options_mut(&mut self) -> &mut web_sys::AddEventListenerOptions {
self.options
.get_or_insert_with(web_sys::AddEventListenerOptions::new)
}
}
pub trait DOMEventResponder: Sized {
fn add<E: EventDescriptor + 'static>(
self,
event: E,
handler: impl FnMut(E::EventType) + 'static,
) -> Self;
#[inline]
fn add_handler(self, handler: impl EventHandler) -> Self {
handler.attach(self)
}
}
impl<T> DOMEventResponder for crate::HtmlElement<T>
where
T: crate::html::ElementDescriptor + 'static,
{
#[inline(always)]
fn add<E: EventDescriptor + 'static>(
self,
event: E,
handler: impl FnMut(E::EventType) + 'static,
) -> Self {
self.on(event, handler)
}
}
impl DOMEventResponder for crate::View {
#[inline(always)]
fn add<E: EventDescriptor + 'static>(
self,
event: E,
handler: impl FnMut(E::EventType) + 'static,
) -> Self {
self.on(event, handler)
}
}
pub enum EventHandlerFn {
Keydown(Box<dyn FnMut(KeyboardEvent)>),
Keyup(Box<dyn FnMut(KeyboardEvent)>),
Keypress(Box<dyn FnMut(KeyboardEvent)>),
Click(Box<dyn FnMut(MouseEvent)>),
Dblclick(Box<dyn FnMut(MouseEvent)>),
Mousedown(Box<dyn FnMut(MouseEvent)>),
Mouseup(Box<dyn FnMut(MouseEvent)>),
Mouseenter(Box<dyn FnMut(MouseEvent)>),
Mouseleave(Box<dyn FnMut(MouseEvent)>),
Mouseout(Box<dyn FnMut(MouseEvent)>),
Mouseover(Box<dyn FnMut(MouseEvent)>),
Mousemove(Box<dyn FnMut(MouseEvent)>),
Wheel(Box<dyn FnMut(WheelEvent)>),
Touchstart(Box<dyn FnMut(TouchEvent)>),
Touchend(Box<dyn FnMut(TouchEvent)>),
Touchcancel(Box<dyn FnMut(TouchEvent)>),
Touchmove(Box<dyn FnMut(TouchEvent)>),
Pointerenter(Box<dyn FnMut(PointerEvent)>),
Pointerleave(Box<dyn FnMut(PointerEvent)>),
Pointerdown(Box<dyn FnMut(PointerEvent)>),
Pointerup(Box<dyn FnMut(PointerEvent)>),
Pointercancel(Box<dyn FnMut(PointerEvent)>),
Pointerout(Box<dyn FnMut(PointerEvent)>),
Pointerover(Box<dyn FnMut(PointerEvent)>),
Pointermove(Box<dyn FnMut(PointerEvent)>),
Drag(Box<dyn FnMut(DragEvent)>),
Dragend(Box<dyn FnMut(DragEvent)>),
Dragenter(Box<dyn FnMut(DragEvent)>),
Dragleave(Box<dyn FnMut(DragEvent)>),
Dragstart(Box<dyn FnMut(DragEvent)>),
Drop(Box<dyn FnMut(DragEvent)>),
Blur(Box<dyn FnMut(FocusEvent)>),
Focusout(Box<dyn FnMut(FocusEvent)>),
Focus(Box<dyn FnMut(FocusEvent)>),
Focusin(Box<dyn FnMut(FocusEvent)>),
}
pub trait EventHandler {
fn attach<T: DOMEventResponder>(self, target: T) -> T;
}
impl<T, const N: usize> EventHandler for [T; N]
where
T: EventHandler,
{
#[inline]
fn attach<R: DOMEventResponder>(self, target: R) -> R {
let mut target = target;
for item in self {
target = item.attach(target);
}
target
}
}
impl<T> EventHandler for Option<T>
where
T: EventHandler,
{
#[inline]
fn attach<R: DOMEventResponder>(self, target: R) -> R {
match self {
Some(event_handler) => event_handler.attach(target),
None => target,
}
}
}
macro_rules! tc {
($($ty:ident),*) => {
impl<$($ty),*> EventHandler for ($($ty,)*)
where
$($ty: EventHandler),*
{
#[inline]
fn attach<RES: DOMEventResponder>(self, target: RES) -> RES {
::paste::paste! {
let (
$(
[<$ty:lower>],)*
) = self;
$(
let target = [<$ty:lower>].attach(target);
)*
target
}
}
}
};
}
tc!(A);
tc!(A, B);
tc!(A, B, C);
tc!(A, B, C, D);
tc!(A, B, C, D, E);
tc!(A, B, C, D, E, F);
tc!(A, B, C, D, E, F, G);
tc!(A, B, C, D, E, F, G, H);
tc!(A, B, C, D, E, F, G, H, I);
tc!(A, B, C, D, E, F, G, H, I, J);
tc!(A, B, C, D, E, F, G, H, I, J, K);
tc!(A, B, C, D, E, F, G, H, I, J, K, L);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y);
#[rustfmt::skip]
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
macro_rules! collection_callback {
{$(
$collection:ident
),* $(,)?} => {
$(
impl<T> EventHandler for $collection<T>
where
T: EventHandler
{
#[inline]
fn attach<R: DOMEventResponder>(self, target: R) -> R {
let mut target = target;
for item in self {
target = item.attach(target);
}
target
}
}
)*
};
}
use std::collections::{BTreeSet, BinaryHeap, HashSet, LinkedList, VecDeque};
collection_callback! {
Vec,
BTreeSet,
BinaryHeap,
HashSet,
LinkedList,
VecDeque,
}
macro_rules! generate_event_types {
{$(
$( #[$does_not_bubble:ident] )?
$( $event:ident )+ : $web_event:ident
),* $(,)?} => {
::paste::paste! {
$(
#[doc = "The `" [< $($event)+ >] "` event, which receives [" $web_event "](web_sys::" $web_event ") as its argument."]
#[derive(Copy, Clone, Debug)]
#[allow(non_camel_case_types)]
pub struct [<$( $event )+ >];
impl EventDescriptor for [< $($event)+ >] {
type EventType = web_sys::$web_event;
#[inline(always)]
fn name(&self) -> Oco<'static, str> {
stringify!([< $($event)+ >]).into()
}
#[inline(always)]
fn event_delegation_key(&self) -> Oco<'static, str> {
concat!("$$$", stringify!([< $($event)+ >])).into()
}
const BUBBLES: bool = true $(&& generate_event_types!($does_not_bubble))?;
}
)*
#[non_exhaustive]
pub enum GenericEventHandler {
$(
#[doc = "Variant mapping [`struct@" [< $($event)+ >] "`] to its event handler type."]
[< $($event:camel)+ >]([< $($event)+ >], Box<dyn FnMut($web_event) + 'static>),
)*
}
impl ::core::fmt::Debug for GenericEventHandler {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
$(
Self::[< $($event:camel)+ >](event, _) => f
.debug_tuple(stringify!([< $($event:camel)+ >]))
.field(&event)
.field(&::std::any::type_name::<Box<dyn FnMut($web_event) + 'static>>())
.finish(),
)*
}
}
}
impl EventHandler for GenericEventHandler {
fn attach<T: DOMEventResponder>(self, target: T) -> T {
match self {
$(
Self::[< $($event:camel)+ >](event, handler) => target.add(event, handler),
)*
}
}
}
$(
impl<F> From<([< $($event)+ >], F)> for GenericEventHandler
where
F: FnMut($web_event) + 'static
{
fn from(value: ([< $($event)+ >], F)) -> Self {
Self::[< $($event:camel)+ >](value.0, Box::new(value.1))
}
}
impl<F> EventHandler for ([< $($event)+ >], F)
where
F: FnMut($web_event) + 'static
{
fn attach<L: DOMEventResponder>(self, target: L) -> L {
target.add(self.0, self.1)
}
}
)*
}
};
(does_not_bubble) => { false }
}
generate_event_types! {
#[does_not_bubble]
after print: Event,
#[does_not_bubble]
before print: Event,
#[does_not_bubble]
before unload: BeforeUnloadEvent,
#[does_not_bubble]
gamepad connected: GamepadEvent,
#[does_not_bubble]
gamepad disconnected: GamepadEvent,
hash change: HashChangeEvent,
#[does_not_bubble]
language change: Event,
#[does_not_bubble]
message: MessageEvent,
#[does_not_bubble]
message error: MessageEvent,
#[does_not_bubble]
offline: Event,
#[does_not_bubble]
online: Event,
#[does_not_bubble]
page hide: PageTransitionEvent,
#[does_not_bubble]
page show: PageTransitionEvent,
pop state: PopStateEvent,
rejection handled: PromiseRejectionEvent,
#[does_not_bubble]
storage: StorageEvent,
#[does_not_bubble]
unhandled rejection: PromiseRejectionEvent,
#[does_not_bubble]
unload: Event,
#[does_not_bubble]
abort: UiEvent,
animation cancel: AnimationEvent,
animation end: AnimationEvent,
animation iteration: AnimationEvent,
animation start: AnimationEvent,
aux click: MouseEvent,
before input: InputEvent,
#[does_not_bubble]
blur: FocusEvent,
#[does_not_bubble]
can play: Event,
#[does_not_bubble]
can play through: Event,
change: Event,
click: MouseEvent,
#[does_not_bubble]
close: Event,
composition end: CompositionEvent,
composition start: CompositionEvent,
composition update: CompositionEvent,
context menu: MouseEvent,
#[does_not_bubble]
cue change: Event,
dbl click: MouseEvent,
drag: DragEvent,
drag end: DragEvent,
drag enter: DragEvent,
drag leave: DragEvent,
drag over: DragEvent,
drag start: DragEvent,
drop: DragEvent,
#[does_not_bubble]
duration change: Event,
#[does_not_bubble]
emptied: Event,
#[does_not_bubble]
ended: Event,
#[does_not_bubble]
error: ErrorEvent,
#[does_not_bubble]
focus: FocusEvent,
#[does_not_bubble]
focus in: FocusEvent,
#[does_not_bubble]
focus out: FocusEvent,
form data: Event, #[does_not_bubble]
got pointer capture: PointerEvent,
input: Event,
#[does_not_bubble]
invalid: Event,
key down: KeyboardEvent,
key press: KeyboardEvent,
key up: KeyboardEvent,
#[does_not_bubble]
load: Event,
#[does_not_bubble]
loaded data: Event,
#[does_not_bubble]
loaded metadata: Event,
#[does_not_bubble]
load start: Event,
lost pointer capture: PointerEvent,
mouse down: MouseEvent,
#[does_not_bubble]
mouse enter: MouseEvent,
#[does_not_bubble]
mouse leave: MouseEvent,
mouse move: MouseEvent,
mouse out: MouseEvent,
mouse over: MouseEvent,
mouse up: MouseEvent,
#[does_not_bubble]
pause: Event,
#[does_not_bubble]
play: Event,
#[does_not_bubble]
playing: Event,
pointer cancel: PointerEvent,
pointer down: PointerEvent,
#[does_not_bubble]
pointer enter: PointerEvent,
#[does_not_bubble]
pointer leave: PointerEvent,
pointer move: PointerEvent,
pointer out: PointerEvent,
pointer over: PointerEvent,
pointer up: PointerEvent,
#[does_not_bubble]
progress: ProgressEvent,
#[does_not_bubble]
rate change: Event,
reset: Event,
#[does_not_bubble]
resize: UiEvent,
#[does_not_bubble]
scroll: Event,
#[does_not_bubble]
scroll end: Event,
security policy violation: SecurityPolicyViolationEvent,
#[does_not_bubble]
seeked: Event,
#[does_not_bubble]
seeking: Event,
select: Event,
#[does_not_bubble]
selection change: Event,
select start: Event,
slot change: Event,
#[does_not_bubble]
stalled: Event,
submit: SubmitEvent,
#[does_not_bubble]
suspend: Event,
#[does_not_bubble]
time update: Event,
#[does_not_bubble]
toggle: Event,
touch cancel: TouchEvent,
touch end: TouchEvent,
touch move: TouchEvent,
touch start: TouchEvent,
transition cancel: TransitionEvent,
transition end: TransitionEvent,
transition run: TransitionEvent,
transition start: TransitionEvent,
#[does_not_bubble]
volume change: Event,
#[does_not_bubble]
waiting: Event,
webkit animation end: Event,
webkit animation iteration: Event,
webkit animation start: Event,
webkit transition end: Event,
wheel: WheelEvent,
D O M Content Loaded: Event, #[does_not_bubble]
device motion: DeviceMotionEvent,
#[does_not_bubble]
device orientation: DeviceOrientationEvent,
#[does_not_bubble]
orientation change: Event,
copy: Event, cut: Event, paste: Event, fullscreen change: Event,
fullscreen error: Event,
pointer lock change: Event,
pointer lock error: Event,
#[does_not_bubble]
ready state change: Event,
visibility change: Event,
}
pub use web_sys::{
AnimationEvent, BeforeUnloadEvent, CompositionEvent, CustomEvent,
DeviceMotionEvent, DeviceOrientationEvent, DragEvent, ErrorEvent, Event,
FocusEvent, GamepadEvent, HashChangeEvent, InputEvent, KeyboardEvent,
MessageEvent, MouseEvent, PageTransitionEvent, PointerEvent, PopStateEvent,
ProgressEvent, PromiseRejectionEvent, SecurityPolicyViolationEvent,
StorageEvent, SubmitEvent, TouchEvent, TransitionEvent, UiEvent,
WheelEvent,
};