Skip to content

Commit

Permalink
AttributeInspector as a component
Browse files Browse the repository at this point in the history
Also added docs with example.
  • Loading branch information
paul-hansen committed Dec 9, 2024
1 parent e8092ae commit 5fc9021
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 16 deletions.
8 changes: 4 additions & 4 deletions examples/counter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use leptos::{attribute_interceptor::AttrInterceptor, prelude::*};
use leptos::prelude::*;

/// A simple counter component.
///
Expand All @@ -12,12 +12,12 @@ pub fn SimpleCounter(
) -> impl IntoView {
let (value, set_value) = signal(initial_value);

AttrInterceptor::new(move |attrs|view! {
view! {
<div>
<button on:click=move |_| set_value.set(0)>"Clear"</button>
<button on:click=move |_| *set_value.write() -= step>"-1"</button>
<span {..attrs}>"Value: " {value} "!"</span>
<span>"Value: " {value} "!"</span>
<button on:click=move |_| set_value.update(|value| *value += step)>"+1"</button>
</div>
})
}
}
2 changes: 1 addition & 1 deletion examples/counter/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|| {
view! { <SimpleCounter initial_value=0 step=1 attr:style="background-color: red;"/> }
view! { <SimpleCounter initial_value=0 step=1/> }
})
}
64 changes: 53 additions & 11 deletions leptos/src/attribute_interceptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,58 @@ use crate::attr::{
use leptos::prelude::*;

/// Function stored to build/rebuild the wrapped children when attributes are added.
pub type AiChildBuilder<T> = dyn Fn(AnyAttribute) -> T + Send + Sync + 'static;
type ChildBuilder<T> = 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. `<MyComponent
/// attr:class="some-class"/>`) 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!{
/// <AttributeInterceptor let:attrs>
/// <div id="wrapper">
/// <div id="inner" {..attrs} />
/// </div>
/// </AttributeInterceptor>
/// }
/// }
/// ```
#[component]
pub fn AttributeInterceptor<Chil, T>(
/// 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.
pub struct AttrInterceptor<T: IntoView, A> {
children_builder: Box<AiChildBuilder<T>>,
struct AttributeInterceptorInner<T: IntoView, A> {
children_builder: Box<ChildBuilder<T>>,
children: T,
attributes: A,
}

impl<T: IntoView> AttrInterceptor<T, ()> {
impl<T: IntoView> AttributeInterceptorInner<T, ()> {
/// 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<F>(children: F) -> Self
Expand All @@ -33,7 +74,7 @@ impl<T: IntoView> AttrInterceptor<T, ()> {
}
}

impl<T: IntoView, A: Attribute> Render for AttrInterceptor<T, A> {
impl<T: IntoView, A: Attribute> Render for AttributeInterceptorInner<T, A> {
type State = <T as Render>::State;

fn build(self) -> Self::State {
Expand All @@ -45,12 +86,12 @@ impl<T: IntoView, A: Attribute> Render for AttrInterceptor<T, A> {
}
}

impl<T: IntoView, A> AddAnyAttr for AttrInterceptor<T, A>
impl<T: IntoView, A> AddAnyAttr for AttributeInterceptorInner<T, A>
where
A: Attribute,
{
type Output<SomeNewAttr: leptos::attr::Attribute> =
AttrInterceptor<T, <<A as NextAttribute>::Output<SomeNewAttr> as Attribute>::CloneableOwned>;
AttributeInterceptorInner<T, <<A as NextAttribute>::Output<SomeNewAttr> as Attribute>::CloneableOwned>;

fn add_any_attr<NewAttr: leptos::attr::Attribute>(
self,
Expand All @@ -65,15 +106,15 @@ where
let children =
(self.children_builder)(attributes.clone().into_any_attr());

AttrInterceptor {
AttributeInterceptorInner {
children_builder: self.children_builder,
children,
attributes,
}
}
}

impl<T: IntoView, A: Attribute> RenderHtml for AttrInterceptor<T, A> {
impl<T: IntoView, A: Attribute> RenderHtml for AttributeInterceptorInner<T, A> {
type AsyncOutput = T::AsyncOutput;

const MIN_LENGTH: usize = T::MIN_LENGTH;
Expand All @@ -82,7 +123,9 @@ impl<T: IntoView, A: Attribute> RenderHtml for AttrInterceptor<T, A> {
self.children.dry_resolve()
}

fn resolve(self) -> impl std::future::Future<Output = Self::AsyncOutput> + Send {
fn resolve(
self,
) -> impl std::future::Future<Output = Self::AsyncOutput> + Send {
self.children.resolve()
}

Expand All @@ -105,4 +148,3 @@ impl<T: IntoView, A: Attribute> RenderHtml for AttrInterceptor<T, A> {
self.children.hydrate::<FROM_SERVER>(cursor, position)
}
}

0 comments on commit 5fc9021

Please sign in to comment.