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