Skip to main content

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