From 7f95516551329682c06b2423772e54ab787dc618 Mon Sep 17 00:00:00 2001 From: Lukas Potthast Date: Tue, 26 Mar 2024 11:43:26 +0100 Subject: [PATCH] Rename Prop -> Binding; Fix formatting; Keep but deprecate `attrs` fn; Handle mouse, keyboard, touch, pointer, drag and focus events; --- examples/spread/src/lib.rs | 31 +++-- examples/spread/src/main.rs | 2 +- leptos/src/lib.rs | 2 +- leptos_dom/src/events/typed.rs | 80 +++++++++++- leptos_dom/src/html.rs | 167 +++++++++++++++++++++--- leptos_macro/src/view/client_builder.rs | 2 +- router/src/lib.rs | 2 +- server_fn_macro/src/lib.rs | 13 +- 8 files changed, 255 insertions(+), 44 deletions(-) diff --git a/examples/spread/src/lib.rs b/examples/spread/src/lib.rs index 3efc9f5e8c..81def00abf 100644 --- a/examples/spread/src/lib.rs +++ b/examples/spread/src/lib.rs @@ -12,35 +12,46 @@ pub fn SpreadingExample() -> impl IntoView { ("aria-disabled", Attribute::String(Oco::Borrowed("false"))), ]; - let event_handlers_only: Vec = vec![ - EventHandlerFn::Click(Box::new(|_e: ev::MouseEvent| { + let event_handlers_only: Vec = + vec![EventHandlerFn::Click(Box::new(|_e: ev::MouseEvent| { alert("event_handlers_only clicked"); - })), - ]; + }))]; - let mixed: Vec = vec![ + let mixed: Vec = 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 = + vec![EventHandlerFn::Click(Box::new(|_e: ev::MouseEvent| { + alert("partial_event_handlers clicked"); + }))]; + view! {
- "attrs_only" + "
"
- "event_handlers_only" + "
"
- "mixed" + "
" +
+ +
+ "
"
// 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. //
// "with overwritten click handler" //
diff --git a/examples/spread/src/main.rs b/examples/spread/src/main.rs index 75428f207b..afde734c86 100644 --- a/examples/spread/src/main.rs +++ b/examples/spread/src/main.rs @@ -1,5 +1,5 @@ -use spread::SpreadingExample; use leptos::*; +use spread::SpreadingExample; pub fn main() { _ = console_log::init_with_level(log::Level::Debug); diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index ee964ed71d..ded93201e1 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -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, diff --git a/leptos_dom/src/events/typed.rs b/leptos_dom/src/events/typed.rs index dda161040e..c981afbbf1 100644 --- a/leptos_dom/src/events/typed.rs +++ b/leptos_dom/src/events/typed.rs @@ -169,10 +169,82 @@ impl DOMEventResponder for crate::View { /// A statically typed event handler. pub enum EventHandlerFn { - /// Keydown event handler. - Keydown(Box), - /// Click event handler. - Click(Box), + /// `keydown` event handler. + Keydown(Box), + /// `keyup` event handler. + Keyup(Box), + /// `keypress` event handler. + Keypress(Box), + + /// `click` event handler. + Click(Box), + /// `dblclick` event handler. + Dblclick(Box), + /// `mousedown` event handler. + Mousedown(Box), + /// `mouseup` event handler. + Mouseup(Box), + /// `mouseenter` event handler. + Mouseenter(Box), + /// `mouseleave` event handler. + Mouseleave(Box), + /// `mouseout` event handler. + Mouseout(Box), + /// `mouseover` event handler. + Mouseover(Box), + /// `mousemove` event handler. + Mousemove(Box), + + /// `wheel` event handler. + Wheel(Box), + + /// `touchstart` event handler. + Touchstart(Box), + /// `touchend` event handler. + Touchend(Box), + /// `touchcancel` event handler. + Touchcancel(Box), + /// `touchmove` event handler. + Touchmove(Box), + + /// `pointerenter` event handler. + Pointerenter(Box), + /// `pointerleave` event handler. + Pointerleave(Box), + /// `pointerdown` event handler. + Pointerdown(Box), + /// `pointerup` event handler. + Pointerup(Box), + /// `pointercancel` event handler. + Pointercancel(Box), + /// `pointerout` event handler. + Pointerout(Box), + /// `pointerover` event handler. + Pointerover(Box), + /// `pointermove` event handler. + Pointermove(Box), + + /// `drag` event handler. + Drag(Box), + /// `dragend` event handler. + Dragend(Box), + /// `dragenter` event handler. + Dragenter(Box), + /// `dragleave` event handler. + Dragleave(Box), + /// `dragstart` event handler. + Dragstart(Box), + /// `drop` event handler. + Drop(Box), + + /// `blur` event handler. + Blur(Box), + /// `focusout` event handler. + Focusout(Box), + /// `focus` event handler. + Focus(Box), + /// `focusin` event handler. + Focusin(Box), } /// Type that can be used to handle DOM events diff --git a/leptos_dom/src/html.rs b/leptos_dom/src/html.rs index 0e98eeca5f..1ea0f4509f 100644 --- a/leptos_dom/src/html.rs +++ b/leptos_dom/src/html.rs @@ -366,22 +366,27 @@ where } } -/// Represents HTML element properties. These can either be named attributes or event handlers. -/// A collection of Props (`collection: Vec`) can be spread onto an element like in `view! {
}`. -pub enum Prop { +/// Bind data through attributes or behavior through event handlers to an element. +/// A collection of bindings (`collection: Vec`) can be spread onto an element like in `view! {
}`. +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 for Prop { +impl From for Binding { fn from(handler: EventHandlerFn) -> Self { Self::EventHandler(handler) } @@ -672,25 +677,149 @@ impl HtmlElement { } } - /// Adds multiple properties to the element. + /// Adds multiple attributes to the element. #[track_caller] - pub fn props>( + #[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, + attrs: impl std::iter::IntoIterator, ) -> 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>( + mut self, + bindings: impl std::iter::IntoIterator, + ) -> 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` diff --git a/leptos_macro/src/view/client_builder.rs b/leptos_macro/src/view/client_builder.rs index 4e31a488a1..e6ef7ad800 100644 --- a/leptos_macro/src/view/client_builder.rs +++ b/leptos_macro/src/view/client_builder.rs @@ -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 { diff --git a/router/src/lib.rs b/router/src/lib.rs index b77f0e59fd..f05261688f 100644 --- a/router/src/lib.rs +++ b/router/src/lib.rs @@ -28,7 +28,7 @@ //! ## Example //! //! ```rust -//! +//! //! use leptos::*; //! use leptos_router::*; //! diff --git a/server_fn_macro/src/lib.rs b/server_fn_macro/src/lib.rs index 8560d555b0..c41be02648 100644 --- a/server_fn_macro/src/lib.rs +++ b/server_fn_macro/src/lib.rs @@ -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 {