diff --git a/leptos/src/attribute_interceptor.rs b/leptos/src/attribute_interceptor.rs new file mode 100644 index 0000000000..3341585fa8 --- /dev/null +++ b/leptos/src/attribute_interceptor.rs @@ -0,0 +1,150 @@ +use crate::attr::{ + any_attribute::{AnyAttribute, IntoAnyAttribute}, + Attribute, NextAttribute, +}; +use leptos::prelude::*; + +/// Function stored to build/rebuild the wrapped children when attributes are added. +type ChildBuilder = dyn Fn(AnyAttribute) -> T + Send + Sync + 'static; + +/// Intercepts attributes passed to your component, allowing passing them to any element. +/// +/// By default, Leptos passes any attributes passed to your component (e.g. ``) to the top-level element in the view returned by your component. +/// [`AttributeInterceptor`] allows you to intercept this behavior and pass it onto any element in +/// your component instead. +/// +/// Must be the top level element in your component's view. +/// +/// ## Example +/// +/// Any attributes passed to MyComponent will be passed to the #inner element. +/// +/// ``` +/// # use leptos::prelude::*; +/// use leptos::attribute_interceptor::AttributeInterceptor; +/// +/// #[component] +/// pub fn MyComponent() -> impl IntoView { +/// view! { +/// +///
+///
+///
+/// +/// } +/// } +/// ``` +#[component] +pub fn AttributeInterceptor( + /// The elements that will be rendered, with the attributes this component received as a + /// parameter. + children: Chil, +) -> impl IntoView +where + Chil: Fn(AnyAttribute) -> T + Send + Sync + 'static, + T: IntoView, +{ + AttributeInterceptorInner::new(children) +} + +/// Wrapper to intercept attributes passed to a component so you can apply them to a different +/// element. +struct AttributeInterceptorInner { + children_builder: Box>, + children: T, + attributes: A, +} + +impl AttributeInterceptorInner { + /// Use this as the returned view from your component to collect the attributes that are passed + /// to your component so you can manually handle them. + pub fn new(children: F) -> Self + where + F: Fn(AnyAttribute) -> T + Send + Sync + 'static, + { + let children_builder = Box::new(children); + let children = children_builder(().into_any_attr()); + + Self { + children_builder, + children, + attributes: (), + } + } +} + +impl Render for AttributeInterceptorInner { + type State = ::State; + + fn build(self) -> Self::State { + self.children.build() + } + + fn rebuild(self, state: &mut Self::State) { + self.children.rebuild(state); + } +} + +impl AddAnyAttr for AttributeInterceptorInner +where + A: Attribute, +{ + type Output = + AttributeInterceptorInner::Output as Attribute>::CloneableOwned>; + + fn add_any_attr( + self, + attr: NewAttr, + ) -> Self::Output + where + Self::Output: RenderHtml, + { + let attributes = + self.attributes.add_any_attr(attr).into_cloneable_owned(); + + let children = + (self.children_builder)(attributes.clone().into_any_attr()); + + AttributeInterceptorInner { + children_builder: self.children_builder, + children, + attributes, + } + } +} + +impl RenderHtml for AttributeInterceptorInner { + type AsyncOutput = T::AsyncOutput; + + const MIN_LENGTH: usize = T::MIN_LENGTH; + + fn dry_resolve(&mut self) { + self.children.dry_resolve() + } + + fn resolve( + self, + ) -> impl std::future::Future + Send { + self.children.resolve() + } + + fn to_html_with_buf( + self, + buf: &mut String, + position: &mut leptos::tachys::view::Position, + escape: bool, + mark_branches: bool, + ) { + self.children + .to_html_with_buf(buf, position, escape, mark_branches) + } + + fn hydrate( + self, + cursor: &leptos::tachys::hydration::Cursor, + position: &leptos::tachys::view::PositionState, + ) -> Self::State { + self.children.hydrate::(cursor, position) + } +} diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index 04cf16aee9..57da2bafad 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -192,6 +192,9 @@ pub mod callback; /// Types that can be passed as the `children` prop of a component. pub mod children; +/// Wrapper for intercepting component attributes. +pub mod attribute_interceptor; + #[doc(hidden)] /// Traits used to implement component constructors. pub mod component;