Skip to main content

leptos/
attribute_interceptor.rs

1use crate::attr::{
2    any_attribute::{AnyAttribute, IntoAnyAttribute},
3    Attribute, NextAttribute,
4};
5use leptos::prelude::*;
6
7/// Function stored to build/rebuild the wrapped children when attributes are added.
8type ChildBuilder<T> = dyn Fn(AnyAttribute) -> T + Send + Sync + 'static;
9
10/// Intercepts attributes passed to your component, allowing passing them to any element.
11///
12/// By default, Leptos passes any attributes passed to your component (e.g. `<MyComponent
13/// attr:class="some-class"/>`) to the top-level element in the view returned by your component.
14/// [`AttributeInterceptor`] allows you to intercept this behavior and pass it onto any element in
15/// your component instead.
16///
17/// Must be the top level element in your component's view.
18///
19/// ## Example
20///
21/// Any attributes passed to MyComponent will be passed to the #inner element.
22///
23/// ```
24/// # use leptos::prelude::*;
25/// use leptos::attribute_interceptor::AttributeInterceptor;
26///
27/// #[component]
28/// pub fn MyComponent() -> impl IntoView {
29///     view! {
30///         <AttributeInterceptor let:attrs>
31///             <div id="wrapper">
32///                 <div id="inner" {..attrs} />
33///             </div>
34///         </AttributeInterceptor>
35///     }
36/// }
37/// ```
38#[component(transparent)]
39pub fn AttributeInterceptor<Chil, T>(
40    /// The elements that will be rendered, with the attributes this component received as a
41    /// parameter.
42    children: Chil,
43) -> impl IntoView
44where
45    Chil: Fn(AnyAttribute) -> T + Send + Sync + 'static,
46    T: IntoView + 'static,
47{
48    AttributeInterceptorInner::new(children)
49}
50
51/// Wrapper to intercept attributes passed to a component so you can apply them to a different
52/// element.
53struct AttributeInterceptorInner<T: IntoView, A> {
54    children_builder: Box<ChildBuilder<T>>,
55    children: T,
56    attributes: A,
57}
58
59impl<T: IntoView> AttributeInterceptorInner<T, ()> {
60    /// Use this as the returned view from your component to collect the attributes that are passed
61    /// to your component so you can manually handle them.
62    pub fn new<F>(children: F) -> Self
63    where
64        F: Fn(AnyAttribute) -> T + Send + Sync + 'static,
65    {
66        let children_builder = Box::new(children);
67        let children = children_builder(().into_any_attr());
68
69        Self {
70            children_builder,
71            children,
72            attributes: (),
73        }
74    }
75}
76
77impl<T: IntoView, A: Attribute> Render for AttributeInterceptorInner<T, A> {
78    type State = <T as Render>::State;
79
80    fn build(self) -> Self::State {
81        self.children.build()
82    }
83
84    fn rebuild(self, state: &mut Self::State) {
85        self.children.rebuild(state);
86    }
87}
88
89impl<T: IntoView + 'static, A> AddAnyAttr for AttributeInterceptorInner<T, A>
90where
91    A: Attribute,
92{
93    type Output<SomeNewAttr: leptos::attr::Attribute> =
94        AttributeInterceptorInner<T, <<A as NextAttribute>::Output<SomeNewAttr> as Attribute>::CloneableOwned>;
95
96    fn add_any_attr<NewAttr: leptos::attr::Attribute>(
97        self,
98        attr: NewAttr,
99    ) -> Self::Output<NewAttr>
100    where
101        Self::Output<NewAttr>: RenderHtml,
102    {
103        let attributes =
104            self.attributes.add_any_attr(attr).into_cloneable_owned();
105
106        let children =
107            (self.children_builder)(attributes.clone().into_any_attr());
108
109        AttributeInterceptorInner {
110            children_builder: self.children_builder,
111            children,
112            attributes,
113        }
114    }
115}
116
117impl<T: IntoView + 'static, A: Attribute> RenderHtml
118    for AttributeInterceptorInner<T, A>
119{
120    type AsyncOutput = T::AsyncOutput;
121    type Owned = AttributeInterceptorInner<T, A::CloneableOwned>;
122
123    const MIN_LENGTH: usize = T::MIN_LENGTH;
124
125    fn dry_resolve(&mut self) {
126        self.children.dry_resolve()
127    }
128
129    fn resolve(
130        self,
131    ) -> impl std::future::Future<Output = Self::AsyncOutput> + Send {
132        self.children.resolve()
133    }
134
135    fn to_html_with_buf(
136        self,
137        buf: &mut String,
138        position: &mut leptos::tachys::view::Position,
139        escape: bool,
140        mark_branches: bool,
141        _extra_attrs: Vec<AnyAttribute>,
142    ) {
143        self.children.to_html_with_buf(
144            buf,
145            position,
146            escape,
147            mark_branches,
148            vec![],
149        )
150    }
151
152    fn hydrate<const FROM_SERVER: bool>(
153        self,
154        cursor: &leptos::tachys::hydration::Cursor,
155        position: &leptos::tachys::view::PositionState,
156    ) -> Self::State {
157        self.children.hydrate::<FROM_SERVER>(cursor, position)
158    }
159
160    async fn hydrate_async(
161        self,
162        cursor: &leptos::tachys::hydration::Cursor,
163        position: &leptos::tachys::view::PositionState,
164    ) -> Self::State {
165        self.children.hydrate_async(cursor, position).await
166    }
167
168    fn into_owned(self) -> Self::Owned {
169        AttributeInterceptorInner {
170            children_builder: self.children_builder,
171            children: self.children,
172            attributes: self.attributes.into_cloneable_owned(),
173        }
174    }
175}