Skip to content

Commit

Permalink
Passthrough non plain attributes as well
Browse files Browse the repository at this point in the history
  • Loading branch information
lukechu10 committed Sep 13, 2024
1 parent 42d2d0a commit ac5c262
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 21 deletions.
4 changes: 2 additions & 2 deletions examples/attributes-passthrough/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
}
}
Expand Down
10 changes: 9 additions & 1 deletion packages/sycamore-macro/src/props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
26 changes: 26 additions & 0 deletions packages/sycamore-web/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ impl AttributeValue for Box<dyn AttributeValue> {
/// 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<T> SetAttribute for T
Expand All @@ -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<dyn AttributeValue>)>,
#[allow(clippy::type_complexity)]
event_handlers: Vec<(Cow<'static, str>, Box<dyn FnMut(web_sys::Event)>)>,
}

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 {
Expand All @@ -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);
}
}
}

Expand Down
35 changes: 19 additions & 16 deletions packages/sycamore-web/src/elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl AsHtmlNode for CustomElement {
}
}

impl GlobalAttributes for CustomElement {}
impl GlobalProps for CustomElement {}
impl HtmlGlobalAttributes for CustomElement {}

macro_rules! impl_attribute {
Expand Down Expand Up @@ -111,7 +111,7 @@ macro_rules! impl_element {
}
}

impl GlobalAttributes for [<Html $name:camel>] {}
impl GlobalProps for [<Html $name:camel>] {}
impl HtmlGlobalAttributes for [<Html $name:camel>] {}

#[doc = "Trait that provides attributes for the `<" $name ">` HTML element."]
Expand Down Expand Up @@ -205,7 +205,7 @@ macro_rules! impl_svg_element {
}
}

impl GlobalAttributes for [<Svg $name:camel>] {}
impl GlobalProps for [<Svg $name:camel>] {}
impl SvgGlobalAttributes for [<Svg $name:camel>] {}

#[doc = "Trait that provides attributes for the `<" $name ">` SVG element."]
Expand Down Expand Up @@ -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<MaybeDynString>) -> Self {
self.set_attribute(name, value.into());
Expand All @@ -1131,34 +1130,38 @@ pub trait GlobalAttributes: AsHtmlNode + Sized {
}

/// Set an event handler with `name`.
fn on<T: events::EventDescriptor, R>(
fn on<E: events::EventDescriptor, R>(
mut self,
_: T,
mut handler: impl EventHandler<T, R>,
_: E,
mut handler: impl EventHandler<E, R>,
) -> 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<T: bind::BindDescriptor>(mut self, _: T, signal: Signal<T::ValueTy>) -> Self {
let node = self.as_html_node();
/// Set a two way binding with `name`.
fn bind<E: bind::BindDescriptor>(mut self, _: E, signal: Signal<E::ValueTy>) -> 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(<T::Event as events::EventDescriptor>::NAME.into(), handler);
self.set_event_handler(<E::Event as events::EventDescriptor>::NAME, handler);

self.prop(T::TARGET_PROPERTY, move || signal.get_clone().into())
self.prop(E::TARGET_PROPERTY, move || signal.get_clone().into())
}
}

impl<T: GlobalProps> 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<Cow<'static, str>>) -> Self {
self.as_html_node().set_inner_html(inner_html.into());
Expand Down
4 changes: 2 additions & 2 deletions packages/sycamore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down

0 comments on commit ac5c262

Please sign in to comment.