-
-
Notifications
You must be signed in to change notification settings - Fork 683
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:
AttributeInterceptor
component to allow passing attributes to…
… other elements (#3340)
- Loading branch information
1 parent
a6aa111
commit 0bb825f
Showing
2 changed files
with
153 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<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. | ||
struct AttributeInterceptorInner<T: IntoView, A> { | ||
children_builder: Box<ChildBuilder<T>>, | ||
children: T, | ||
attributes: A, | ||
} | ||
|
||
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 | ||
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<T: IntoView, A: Attribute> Render for AttributeInterceptorInner<T, A> { | ||
type State = <T as Render>::State; | ||
|
||
fn build(self) -> Self::State { | ||
self.children.build() | ||
} | ||
|
||
fn rebuild(self, state: &mut Self::State) { | ||
self.children.rebuild(state); | ||
} | ||
} | ||
|
||
impl<T: IntoView, A> AddAnyAttr for AttributeInterceptorInner<T, A> | ||
where | ||
A: Attribute, | ||
{ | ||
type Output<SomeNewAttr: leptos::attr::Attribute> = | ||
AttributeInterceptorInner<T, <<A as NextAttribute>::Output<SomeNewAttr> as Attribute>::CloneableOwned>; | ||
|
||
fn add_any_attr<NewAttr: leptos::attr::Attribute>( | ||
self, | ||
attr: NewAttr, | ||
) -> Self::Output<NewAttr> | ||
where | ||
Self::Output<NewAttr>: 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<T: IntoView, A: Attribute> RenderHtml for AttributeInterceptorInner<T, A> { | ||
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<Output = Self::AsyncOutput> + 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<const FROM_SERVER: bool>( | ||
self, | ||
cursor: &leptos::tachys::hydration::Cursor, | ||
position: &leptos::tachys::view::PositionState, | ||
) -> Self::State { | ||
self.children.hydrate::<FROM_SERVER>(cursor, position) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters