diff --git a/examples/attributes-passthrough/src/main.rs b/examples/attributes-passthrough/src/main.rs index f015d7f9..bcb31281 100644 --- a/examples/attributes-passthrough/src/main.rs +++ b/examples/attributes-passthrough/src/main.rs @@ -32,13 +32,13 @@ fn App() -> View { div { CustomButton( id="button1", - //on:click=|_| console_log!("Button 1 clicked!"), + on:click=|_| console_log!("Button 1 clicked!"), ) { "Button 1" } CustomButton( id="button2", class="red-button", style="background-color:red;", - //on:click=|_| console_log!("Button 2 clicked!"), + on:click=|_| console_log!("Button 2 clicked!"), ) { "Button 2" } } } diff --git a/packages/sycamore-macro/src/props.rs b/packages/sycamore-macro/src/props.rs index 5309ae39..81b6a63c 100644 --- a/packages/sycamore-macro/src/props.rs +++ b/packages/sycamore-macro/src/props.rs @@ -254,13 +254,21 @@ mod struct_info { AttributeBase::Svg => quote::format_ident!("Svg{tag_camel}Attributes"), }; quote! { + impl #b_generics_impl ::sycamore::web::GlobalAttributes for #builder_name #b_generics_ty #b_generics_where {} impl #b_generics_impl ::sycamore::web::#base_trait_ident for #builder_name #b_generics_ty #b_generics_where {} impl #b_generics_impl ::sycamore::web::tags::#tag_trait_ident for #builder_name #b_generics_ty #b_generics_where {} impl #b_generics_impl ::sycamore::web::SetAttribute for #builder_name #b_generics_ty #b_generics_where { - fn set_attribute(&mut self, name: &'static str, value: impl ::sycamore::web::AttributeValue) { + fn set_attribute(&mut self, name: &'static ::std::primitive::str, value: impl ::sycamore::web::AttributeValue) { self.attributes.set_attribute(name, value); } + fn set_event_handler( + &mut self, + name: &'static ::std::primitive::str, + handler: impl ::std::ops::FnMut(::sycamore::rt::Event) + 'static, + ) { + self.attributes.set_event_handler(name, handler); + } } } } else { diff --git a/packages/sycamore-web/src/attributes.rs b/packages/sycamore-web/src/attributes.rs index 3176e184..c81265cf 100644 --- a/packages/sycamore-web/src/attributes.rs +++ b/packages/sycamore-web/src/attributes.rs @@ -49,6 +49,11 @@ impl AttributeValue for Box { /// Implemented for all types that can accept attributes ([`AttributeValue`]). pub trait SetAttribute { fn set_attribute(&mut self, name: &'static str, value: impl AttributeValue); + fn set_event_handler( + &mut self, + name: &'static str, + value: impl FnMut(web_sys::Event) + 'static, + ); } impl SetAttribute for T @@ -58,18 +63,36 @@ where fn set_attribute(&mut self, name: &'static str, value: impl AttributeValue) { value.set_self(self.as_html_node(), name.into()); } + + fn set_event_handler( + &mut self, + name: &'static str, + value: impl FnMut(web_sys::Event) + 'static, + ) { + self.as_html_node().set_event_handler(name.into(), value); + } } /// A special prop type that can be used to spread attributes onto an element. #[derive(Default)] pub struct Attributes { values: Vec<(Cow<'static, str>, Box)>, + #[allow(clippy::type_complexity)] + event_handlers: Vec<(Cow<'static, str>, Box)>, } impl SetAttribute for Attributes { fn set_attribute(&mut self, name: &'static str, value: impl AttributeValue) { self.values.push((name.into(), Box::new(value))); } + + fn set_event_handler( + &mut self, + name: &'static str, + value: impl FnMut(web_sys::Event) + 'static, + ) { + self.event_handlers.push((name.into(), Box::new(value))); + } } impl Attributes { @@ -82,6 +105,9 @@ impl Attributes { for (name, value) in self.values { value.set_self(el, name); } + for (name, handler) in self.event_handlers { + el.set_event_handler(name, handler); + } } } diff --git a/packages/sycamore-web/src/elements.rs b/packages/sycamore-web/src/elements.rs index 5b3143f9..e4b29398 100644 --- a/packages/sycamore-web/src/elements.rs +++ b/packages/sycamore-web/src/elements.rs @@ -35,7 +35,7 @@ impl AsHtmlNode for CustomElement { } } -impl GlobalAttributes for CustomElement {} +impl GlobalProps for CustomElement {} impl HtmlGlobalAttributes for CustomElement {} macro_rules! impl_attribute { @@ -111,7 +111,7 @@ macro_rules! impl_element { } } - impl GlobalAttributes for [] {} + impl GlobalProps for [] {} impl HtmlGlobalAttributes for [] {} #[doc = "Trait that provides attributes for the `<" $name ">` HTML element."] @@ -205,7 +205,7 @@ macro_rules! impl_svg_element { } } - impl GlobalAttributes for [] {} + impl GlobalProps for [] {} impl SvgGlobalAttributes for [] {} #[doc = "Trait that provides attributes for the `<" $name ">` SVG element."] @@ -1110,8 +1110,7 @@ pub trait SvgGlobalAttributes: SetAttribute + Sized { } /// Attributes that are available on all elements. -#[allow(private_bounds)] -pub trait GlobalAttributes: AsHtmlNode + Sized { +pub trait GlobalAttributes: SetAttribute + Sized { /// Set attribute `name` with `value`. fn attr(mut self, name: &'static str, value: impl Into) -> Self { self.set_attribute(name, value.into()); @@ -1131,34 +1130,38 @@ pub trait GlobalAttributes: AsHtmlNode + Sized { } /// Set an event handler with `name`. - fn on( + fn on( mut self, - _: T, - mut handler: impl EventHandler, + _: E, + mut handler: impl EventHandler, ) -> Self { let scope = use_current_scope(); // Run handler inside the current scope. let handler = move |ev: web_sys::Event| scope.run_in(|| handler.call(ev.unchecked_into())); - let node = self.as_html_node(); - node.set_event_handler(T::NAME.into(), handler); + self.set_event_handler(E::NAME, handler); self } - fn bind(mut self, _: T, signal: Signal) -> Self { - let node = self.as_html_node(); + /// Set a two way binding with `name`. + fn bind(mut self, _: E, signal: Signal) -> Self { let scope = use_current_scope(); // Run handler inside the current scope. let handler = move |ev: web_sys::Event| { scope.run_in(|| { let value = - js_sys::Reflect::get(&ev.current_target().unwrap(), &T::TARGET_PROPERTY.into()) + js_sys::Reflect::get(&ev.current_target().unwrap(), &E::TARGET_PROPERTY.into()) .unwrap(); - signal.set(T::CONVERT_FROM_JS(&value).expect("failed to convert value from js")); + signal.set(E::CONVERT_FROM_JS(&value).expect("failed to convert value from js")); }) }; - node.set_event_handler(::NAME.into(), handler); + self.set_event_handler(::NAME, handler); - self.prop(T::TARGET_PROPERTY, move || signal.get_clone().into()) + self.prop(E::TARGET_PROPERTY, move || signal.get_clone().into()) } +} + +impl GlobalAttributes for T {} +/// Props that are available on all elements. +pub trait GlobalProps: GlobalAttributes + AsHtmlNode + Sized { /// Set the inner html of an element. fn dangerously_set_inner_html(mut self, inner_html: impl Into>) -> Self { self.as_html_node().set_inner_html(inner_html.into()); diff --git a/packages/sycamore/src/lib.rs b/packages/sycamore/src/lib.rs index aa691cbc..325f4239 100644 --- a/packages/sycamore/src/lib.rs +++ b/packages/sycamore/src/lib.rs @@ -90,8 +90,8 @@ pub mod prelude { #[cfg(feature = "web")] pub use sycamore_web::{ console_dbg, console_log, create_node_ref, document, is_not_ssr, is_ssr, on_mount, window, - Attributes, Children, GlobalAttributes, HtmlGlobalAttributes, Indexed, Keyed, NodeRef, - SvgGlobalAttributes, View, + Attributes, Children, GlobalAttributes, GlobalProps, HtmlGlobalAttributes, Indexed, Keyed, + NodeRef, SvgGlobalAttributes, View, }; pub use crate::reactive::*;