tachys/html/
directive.rs

1use super::attribute::{
2    maybe_next_attr_erasure_macros::next_attr_output_type, Attribute,
3    NextAttribute,
4};
5use crate::{
6    html::attribute::{
7        maybe_next_attr_erasure_macros::next_attr_combine, NamedAttributeKey,
8    },
9    prelude::AddAnyAttr,
10    view::{Position, ToTemplate},
11};
12use send_wrapper::SendWrapper;
13use std::{marker::PhantomData, sync::Arc};
14
15/// Adds a directive to the element, which runs some custom logic in the browser when the element
16/// is created or hydrated.
17pub trait DirectiveAttribute<T, P, D>
18where
19    D: IntoDirective<T, P>,
20{
21    /// The type of the element with the directive added.
22    type Output;
23
24    /// Adds a directive to the element, which runs some custom logic in the browser when the element
25    /// is created or hydrated.
26    fn directive(self, handler: D, param: P) -> Self::Output;
27}
28
29impl<V, T, P, D> DirectiveAttribute<T, P, D> for V
30where
31    V: AddAnyAttr,
32    D: IntoDirective<T, P>,
33    P: Clone + 'static,
34    T: 'static,
35{
36    type Output = <Self as AddAnyAttr>::Output<Directive<T, D, P>>;
37
38    fn directive(self, handler: D, param: P) -> Self::Output {
39        self.add_any_attr(directive(handler, param))
40    }
41}
42
43/// Adds a directive to the element, which runs some custom logic in the browser when the element
44/// is created or hydrated.
45#[inline(always)]
46pub fn directive<T, P, D>(handler: D, param: P) -> Directive<T, D, P>
47where
48    D: IntoDirective<T, P>,
49{
50    Directive(Some(SendWrapper::new(DirectiveInner {
51        handler,
52        param,
53        t: PhantomData,
54    })))
55}
56
57/// Custom logic that runs in the browser when the element is created or hydrated.
58#[derive(Debug)]
59pub struct Directive<T, D, P>(Option<SendWrapper<DirectiveInner<T, D, P>>>);
60
61impl<T, D, P> Clone for Directive<T, D, P>
62where
63    P: Clone + 'static,
64    D: Clone,
65{
66    fn clone(&self) -> Self {
67        Self(self.0.clone())
68    }
69}
70
71#[derive(Debug)]
72struct DirectiveInner<T, D, P> {
73    handler: D,
74    param: P,
75    t: PhantomData<T>,
76}
77
78impl<T, D, P> Clone for DirectiveInner<T, D, P>
79where
80    P: Clone + 'static,
81    D: Clone,
82{
83    fn clone(&self) -> Self {
84        Self {
85            handler: self.handler.clone(),
86            param: self.param.clone(),
87            t: PhantomData,
88        }
89    }
90}
91
92impl<T, P, D> Attribute for Directive<T, D, P>
93where
94    D: IntoDirective<T, P>,
95    P: Clone + 'static, // TODO this is just here to make them cloneable
96    T: 'static,
97{
98    const MIN_LENGTH: usize = 0;
99
100    type AsyncOutput = Self;
101    type State = crate::renderer::types::Element;
102    type Cloneable = Directive<T, D::Cloneable, P>;
103    type CloneableOwned = Directive<T, D::Cloneable, P>;
104
105    fn html_len(&self) -> usize {
106        0
107    }
108
109    fn to_html(
110        self,
111        _buf: &mut String,
112        _class: &mut String,
113        _style: &mut String,
114        _inner_html: &mut String,
115    ) {
116    }
117
118    fn hydrate<const FROM_SERVER: bool>(
119        self,
120        el: &crate::renderer::types::Element,
121    ) -> Self::State {
122        let inner = self.0.expect("directive removed early").take();
123        inner.handler.run(el.clone(), inner.param);
124        el.clone()
125    }
126
127    fn build(self, el: &crate::renderer::types::Element) -> Self::State {
128        let inner = self.0.expect("directive removed early").take();
129        inner.handler.run(el.clone(), inner.param);
130        el.clone()
131    }
132
133    fn rebuild(self, state: &mut Self::State) {
134        let inner = self.0.expect("directive removed early").take();
135        inner.handler.run(state.clone(), inner.param);
136    }
137
138    fn into_cloneable(self) -> Self::Cloneable {
139        self.into_cloneable_owned()
140    }
141
142    fn into_cloneable_owned(self) -> Self::CloneableOwned {
143        let inner = self.0.map(|inner| {
144            let DirectiveInner { handler, param, t } = inner.take();
145            SendWrapper::new(DirectiveInner {
146                handler: handler.into_cloneable(),
147                param,
148                t,
149            })
150        });
151        Directive(inner)
152    }
153
154    fn dry_resolve(&mut self) {
155        // dry_resolve() only runs during SSR, and we should use it to
156        // synchronously remove and drop the SendWrapper value
157        // we don't need this value during SSR and leaving it here could drop it
158        // from a different thread
159        self.0.take();
160    }
161
162    async fn resolve(self) -> Self::AsyncOutput {
163        self
164    }
165
166    fn keys(&self) -> Vec<NamedAttributeKey> {
167        vec![]
168    }
169}
170
171impl<T, D, P> NextAttribute for Directive<T, D, P>
172where
173    D: IntoDirective<T, P>,
174    P: Clone + 'static,
175    T: 'static,
176{
177    next_attr_output_type!(Self, NewAttr);
178
179    fn add_any_attr<NewAttr: Attribute>(
180        self,
181        new_attr: NewAttr,
182    ) -> Self::Output<NewAttr> {
183        next_attr_combine!(self, new_attr)
184    }
185}
186
187impl<T, D, P> ToTemplate for Directive<T, D, P> {
188    const CLASS: &'static str = "";
189
190    fn to_template(
191        _buf: &mut String,
192        _class: &mut String,
193        _style: &mut String,
194        _inner_html: &mut String,
195        _position: &mut Position,
196    ) {
197    }
198}
199
200/// Trait for a directive handler function.
201/// This is used so it's possible to use functions with one or two
202/// parameters as directive handlers.
203///
204/// You can use directives like the following.
205///
206/// ```ignore
207/// # use leptos::{*, html::AnyElement};
208///
209/// // This doesn't take an attribute value
210/// fn my_directive(el: crate::renderer::types::Element) {
211///     // do sth
212/// }
213///
214/// // This requires an attribute value
215/// fn another_directive(el: crate::renderer::types::Element, params: i32) {
216///     // do sth
217/// }
218///
219/// #[component]
220/// pub fn MyComponent() -> impl IntoView {
221///     view! {
222///         // no attribute value
223///         <div use:my_directive></div>
224///
225///         // with an attribute value
226///         <div use:another_directive=8></div>
227///     }
228/// }
229/// ```
230///
231/// A directive is just syntactic sugar for
232///
233/// ```ignore
234/// let node_ref = create_node_ref();
235///
236/// create_effect(move |_| {
237///     if let Some(el) = node_ref.get() {
238///         directive_func(el, possibly_some_param);
239///     }
240/// });
241/// ```
242///
243/// A directive can be a function with one or two parameters.
244/// The first is the element the directive is added to and the optional
245/// second is the parameter that is provided in the attribute.
246pub trait IntoDirective<T: ?Sized, P> {
247    /// An equivalent to this directive that is cloneable and owned.
248    type Cloneable: IntoDirective<T, P> + Clone + 'static;
249
250    /// Calls the handler function
251    fn run(&self, el: crate::renderer::types::Element, param: P);
252
253    /// Converts this into a cloneable type.
254    fn into_cloneable(self) -> Self::Cloneable;
255}
256
257impl<F> IntoDirective<(crate::renderer::types::Element,), ()> for F
258where
259    F: Fn(crate::renderer::types::Element) + 'static,
260{
261    type Cloneable = Arc<dyn Fn(crate::renderer::types::Element)>;
262
263    fn run(&self, el: crate::renderer::types::Element, _: ()) {
264        self(el)
265    }
266
267    fn into_cloneable(self) -> Self::Cloneable {
268        Arc::new(self)
269    }
270}
271
272impl IntoDirective<(crate::renderer::types::Element,), ()>
273    for Arc<dyn Fn(crate::renderer::types::Element)>
274{
275    type Cloneable = Arc<dyn Fn(crate::renderer::types::Element)>;
276
277    fn run(&self, el: crate::renderer::types::Element, _: ()) {
278        self(el)
279    }
280
281    fn into_cloneable(self) -> Self::Cloneable {
282        self
283    }
284}
285
286impl<F, P> IntoDirective<(crate::renderer::types::Element, P), P> for F
287where
288    F: Fn(crate::renderer::types::Element, P) + 'static,
289    P: 'static,
290{
291    type Cloneable = Arc<dyn Fn(crate::renderer::types::Element, P)>;
292
293    fn run(&self, el: crate::renderer::types::Element, param: P) {
294        self(el, param);
295    }
296
297    fn into_cloneable(self) -> Self::Cloneable {
298        Arc::new(self)
299    }
300}
301
302impl<P> IntoDirective<(crate::renderer::types::Element, P), P>
303    for Arc<dyn Fn(crate::renderer::types::Element, P)>
304where
305    P: 'static,
306{
307    type Cloneable = Arc<dyn Fn(crate::renderer::types::Element, P)>;
308
309    fn run(&self, el: crate::renderer::types::Element, param: P) {
310        self(el, param)
311    }
312
313    fn into_cloneable(self) -> Self::Cloneable {
314        self
315    }
316}