Skip to content

Commit

Permalink
Rename Prop -> Binding; Fix formatting; Keep but deprecate attrs fn…
Browse files Browse the repository at this point in the history
…; Handle mouse, keyboard, touch, pointer, drag and focus events;
  • Loading branch information
lpotthast committed Mar 26, 2024
1 parent cbaae1f commit 7f95516
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 44 deletions.
31 changes: 21 additions & 10 deletions examples/spread/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,46 @@ pub fn SpreadingExample() -> impl IntoView {
("aria-disabled", Attribute::String(Oco::Borrowed("false"))),
];

let event_handlers_only: Vec<EventHandlerFn> = vec![
EventHandlerFn::Click(Box::new(|_e: ev::MouseEvent| {
let event_handlers_only: Vec<EventHandlerFn> =
vec![EventHandlerFn::Click(Box::new(|_e: ev::MouseEvent| {
alert("event_handlers_only clicked");
})),
];
}))];

let mixed: Vec<Prop> = vec![
let mixed: Vec<Binding> = vec![
("data-foo", Attribute::String(Oco::Borrowed("123"))).into(),
("aria-disabled", Attribute::String(Oco::Borrowed("true"))).into(),
EventHandlerFn::Click(Box::new(|_e: ev::MouseEvent| {
alert("mixed clicked");
})).into(),
}))
.into(),
];

let partial_attrs: Vec<(&'static str, Attribute)> =
vec![("data-foo", Attribute::String(Oco::Borrowed("11")))];
let partial_event_handlers: Vec<EventHandlerFn> =
vec![EventHandlerFn::Click(Box::new(|_e: ev::MouseEvent| {
alert("partial_event_handlers clicked");
}))];

view! {
<div {..attrs_only}>
"attrs_only"
"<div {..attrs_only} />"
</div>

<div {..event_handlers_only}>
"event_handlers_only"
"<div {..event_handlers_only} />"
</div>

<div {..mixed}>
"mixed"
"<div {..mixed} />"
</div>

<div {..partial_attrs} {..partial_event_handlers}>
"<div {..partial_attrs} {..partial_event_handlers} />"
</div>

// Overwriting an event handler, here on:click, will result in a panic in debug builds. In release builds, the initial handler is kept.
// If spreading is used, prefer manually merging event handlers in the prop list instead.
// If spreading is used, prefer manually merging event handlers in the binding list instead.
//<div {..mixed} on:click=|_e| { alert("I will never be seen..."); }>
// "with overwritten click handler"
//</div>
Expand Down
2 changes: 1 addition & 1 deletion examples/spread/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use spread::SpreadingExample;
use leptos::*;
use spread::SpreadingExample;

pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
Expand Down
2 changes: 1 addition & 1 deletion leptos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ pub use leptos_dom::{
window_event_listener, window_event_listener_untyped,
},
html,
html::Prop,
html::Binding,
math, mount_to, mount_to_body, nonce, svg, window, Attribute, Class,
CollectView, Errors, EventHandlerFn, Fragment, HtmlElement, IntoAttribute,
IntoClass, IntoProperty, IntoStyle, IntoView, NodeRef, Property, View,
Expand Down
80 changes: 76 additions & 4 deletions leptos_dom/src/events/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,82 @@ impl DOMEventResponder for crate::View {

/// A statically typed event handler.
pub enum EventHandlerFn {
/// Keydown event handler.
Keydown(Box<dyn FnMut(KeyboardEvent)>),
/// Click event handler.
Click(Box<dyn FnMut(MouseEvent)>),
/// `keydown` event handler.
Keydown(Box<dyn FnMut(KeyboardEvent)>),
/// `keyup` event handler.
Keyup(Box<dyn FnMut(KeyboardEvent)>),
/// `keypress` event handler.
Keypress(Box<dyn FnMut(KeyboardEvent)>),

/// `click` event handler.
Click(Box<dyn FnMut(MouseEvent)>),
/// `dblclick` event handler.
Dblclick(Box<dyn FnMut(MouseEvent)>),
/// `mousedown` event handler.
Mousedown(Box<dyn FnMut(MouseEvent)>),
/// `mouseup` event handler.
Mouseup(Box<dyn FnMut(MouseEvent)>),
/// `mouseenter` event handler.
Mouseenter(Box<dyn FnMut(MouseEvent)>),
/// `mouseleave` event handler.
Mouseleave(Box<dyn FnMut(MouseEvent)>),
/// `mouseout` event handler.
Mouseout(Box<dyn FnMut(MouseEvent)>),
/// `mouseover` event handler.
Mouseover(Box<dyn FnMut(MouseEvent)>),
/// `mousemove` event handler.
Mousemove(Box<dyn FnMut(MouseEvent)>),

/// `wheel` event handler.
Wheel(Box<dyn FnMut(WheelEvent)>),

/// `touchstart` event handler.
Touchstart(Box<dyn FnMut(TouchEvent)>),
/// `touchend` event handler.
Touchend(Box<dyn FnMut(TouchEvent)>),
/// `touchcancel` event handler.
Touchcancel(Box<dyn FnMut(TouchEvent)>),
/// `touchmove` event handler.
Touchmove(Box<dyn FnMut(TouchEvent)>),

/// `pointerenter` event handler.
Pointerenter(Box<dyn FnMut(PointerEvent)>),
/// `pointerleave` event handler.
Pointerleave(Box<dyn FnMut(PointerEvent)>),
/// `pointerdown` event handler.
Pointerdown(Box<dyn FnMut(PointerEvent)>),
/// `pointerup` event handler.
Pointerup(Box<dyn FnMut(PointerEvent)>),
/// `pointercancel` event handler.
Pointercancel(Box<dyn FnMut(PointerEvent)>),
/// `pointerout` event handler.
Pointerout(Box<dyn FnMut(PointerEvent)>),
/// `pointerover` event handler.
Pointerover(Box<dyn FnMut(PointerEvent)>),
/// `pointermove` event handler.
Pointermove(Box<dyn FnMut(PointerEvent)>),

/// `drag` event handler.
Drag(Box<dyn FnMut(DragEvent)>),
/// `dragend` event handler.
Dragend(Box<dyn FnMut(DragEvent)>),
/// `dragenter` event handler.
Dragenter(Box<dyn FnMut(DragEvent)>),
/// `dragleave` event handler.
Dragleave(Box<dyn FnMut(DragEvent)>),
/// `dragstart` event handler.
Dragstart(Box<dyn FnMut(DragEvent)>),
/// `drop` event handler.
Drop(Box<dyn FnMut(DragEvent)>),

/// `blur` event handler.
Blur(Box<dyn FnMut(FocusEvent)>),
/// `focusout` event handler.
Focusout(Box<dyn FnMut(FocusEvent)>),
/// `focus` event handler.
Focus(Box<dyn FnMut(FocusEvent)>),
/// `focusin` event handler.
Focusin(Box<dyn FnMut(FocusEvent)>),
}

/// Type that can be used to handle DOM events
Expand Down
167 changes: 148 additions & 19 deletions leptos_dom/src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,22 +366,27 @@ where
}
}

/// Represents HTML element properties. These can either be named attributes or event handlers.
/// A collection of Props (`collection: Vec<Prop>`) can be spread onto an element like in `view! { <div {..collection} /> }`.
pub enum Prop {
/// Bind data through attributes or behavior through event handlers to an element.
/// A collection of bindings (`collection: Vec<Binding>`) can be spread onto an element like in `view! { <div {..collection} /> }`.
pub enum Binding {
/// A statically named attribute.
NamedAttr((&'static str, Attribute)),
Attribute {
/// Name of the attribute.
name: &'static str,
/// Value of the attribute, possibly reactive.
value: Attribute,
},
/// A statically typed event handler.
EventHandler(EventHandlerFn),
}

impl From<(&'static str, Attribute)> for Prop {
impl From<(&'static str, Attribute)> for Binding {
fn from((name, value): (&'static str, Attribute)) -> Self {
Self::NamedAttr((name, value))
Self::Attribute { name, value }
}
}

impl From<EventHandlerFn> for Prop {
impl From<EventHandlerFn> for Binding {
fn from(handler: EventHandlerFn) -> Self {
Self::EventHandler(handler)
}
Expand Down Expand Up @@ -672,25 +677,149 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
}
}

/// Adds multiple properties to the element.
/// Adds multiple attributes to the element.
#[track_caller]
pub fn props<P: Into<Prop>>(
#[deprecated(
since = "0.6.10",
note = "Please call `bindings` instead. It can act as a drop-in-replacement but can handle both attributes and event handlers at the same time."
)]
pub fn attrs(
mut self,
props: impl std::iter::IntoIterator<Item = P>,
attrs: impl std::iter::IntoIterator<Item = (&'static str, Attribute)>,
) -> Self {
for prop in props {
let prop = prop.into();
self = match prop {
Prop::NamedAttr((name, value)) => self.attr(name, value),
Prop::EventHandler(handler) => match handler {
EventHandlerFn::Keydown(handler) => self.on(crate::events::typed::keydown, handler),
EventHandlerFn::Click(handler) => self.on(crate::events::typed::click, handler),
},
};
for (name, value) in attrs {
self = self.attr(name, value);
}
self
}

/// Adds multiple bindings (attributes or event handlers) to the element.
#[track_caller]
pub fn bindings<B: Into<Binding>>(
mut self,
bindings: impl std::iter::IntoIterator<Item = B>,
) -> Self {
for binding in bindings {
self = self.binding(binding.into());
}
self
}

/// Add a single binding (attribute or event handler) to the element.
#[track_caller]
fn binding(self, binding: Binding) -> Self {
match binding {
Binding::Attribute { name, value } => self.attr(name, value),
Binding::EventHandler(handler) => match handler {
EventHandlerFn::Keydown(handler) => {
self.on(crate::events::typed::keydown, handler)
}
EventHandlerFn::Keyup(handler) => {
self.on(crate::events::typed::keyup, handler)
}
EventHandlerFn::Keypress(handler) => {
self.on(crate::events::typed::keypress, handler)
}
EventHandlerFn::Click(handler) => {
self.on(crate::events::typed::click, handler)
}
EventHandlerFn::Dblclick(handler) => {
self.on(crate::events::typed::dblclick, handler)
}
EventHandlerFn::Mousedown(handler) => {
self.on(crate::events::typed::mousedown, handler)
}
EventHandlerFn::Mouseup(handler) => {
self.on(crate::events::typed::mouseup, handler)
}
EventHandlerFn::Mouseenter(handler) => {
self.on(crate::events::typed::mouseenter, handler)
}
EventHandlerFn::Mouseleave(handler) => {
self.on(crate::events::typed::mouseleave, handler)
}
EventHandlerFn::Mouseout(handler) => {
self.on(crate::events::typed::mouseout, handler)
}
EventHandlerFn::Mouseover(handler) => {
self.on(crate::events::typed::mouseover, handler)
}
EventHandlerFn::Mousemove(handler) => {
self.on(crate::events::typed::mousemove, handler)
}
EventHandlerFn::Wheel(handler) => {
self.on(crate::events::typed::wheel, handler)
}
EventHandlerFn::Touchstart(handler) => {
self.on(crate::events::typed::touchstart, handler)
}
EventHandlerFn::Touchend(handler) => {
self.on(crate::events::typed::touchend, handler)
}
EventHandlerFn::Touchcancel(handler) => {
self.on(crate::events::typed::touchcancel, handler)
}
EventHandlerFn::Touchmove(handler) => {
self.on(crate::events::typed::touchmove, handler)
}
EventHandlerFn::Pointerenter(handler) => {
self.on(crate::events::typed::pointerenter, handler)
}
EventHandlerFn::Pointerleave(handler) => {
self.on(crate::events::typed::pointerleave, handler)
}
EventHandlerFn::Pointerdown(handler) => {
self.on(crate::events::typed::pointerdown, handler)
}
EventHandlerFn::Pointerup(handler) => {
self.on(crate::events::typed::pointerup, handler)
}
EventHandlerFn::Pointercancel(handler) => {
self.on(crate::events::typed::pointercancel, handler)
}
EventHandlerFn::Pointerout(handler) => {
self.on(crate::events::typed::pointerout, handler)
}
EventHandlerFn::Pointerover(handler) => {
self.on(crate::events::typed::pointerover, handler)
}
EventHandlerFn::Pointermove(handler) => {
self.on(crate::events::typed::pointermove, handler)
}
EventHandlerFn::Drag(handler) => {
self.on(crate::events::typed::drag, handler)
}
EventHandlerFn::Dragend(handler) => {
self.on(crate::events::typed::dragend, handler)
}
EventHandlerFn::Dragenter(handler) => {
self.on(crate::events::typed::dragenter, handler)
}
EventHandlerFn::Dragleave(handler) => {
self.on(crate::events::typed::dragleave, handler)
}
EventHandlerFn::Dragstart(handler) => {
self.on(crate::events::typed::dragstart, handler)
}
EventHandlerFn::Drop(handler) => {
self.on(crate::events::typed::drop, handler)
}
EventHandlerFn::Blur(handler) => {
self.on(crate::events::typed::blur, handler)
}
EventHandlerFn::Focusout(handler) => {
self.on(crate::events::typed::focusout, handler)
}
EventHandlerFn::Focus(handler) => {
self.on(crate::events::typed::focus, handler)
}
EventHandlerFn::Focusin(handler) => {
self.on(crate::events::typed::focusin, handler)
}
},
}
}

/// Adds a class to an element.
///
/// **Note**: In the builder syntax, this will be overwritten by the `class`
Expand Down
2 changes: 1 addition & 1 deletion leptos_macro/src/view/client_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ pub(crate) fn element_to_tokens(
..
}),
_,
) => Some(quote! { .props(#[allow(unused_brace)] {#end}) }),
) => Some(quote! { .bindings(#[allow(unused_brace)] {#end}) }),
_ => None,
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
//! ## Example
//!
//! ```rust
//!
//!
//! use leptos::*;
//! use leptos_router::*;
//!
Expand Down
13 changes: 6 additions & 7 deletions server_fn_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,14 @@ pub fn server_macro_impl(
.inputs
.iter_mut()
.map(|f| {
let typed_arg = match f {
FnArg::Receiver(_) => {
return Err(syn::Error::new(
let typed_arg =
match f {
FnArg::Receiver(_) => return Err(syn::Error::new(
f.span(),
"cannot use receiver types in server function macro",
))
}
FnArg::Typed(t) => t,
};
)),
FnArg::Typed(t) => t,
};

// strip `mut`, which is allowed in fn args but not in struct fields
if let Pat::Ident(ident) = &mut *typed_arg.pat {
Expand Down

0 comments on commit 7f95516

Please sign in to comment.