hotman/
attribute.rs

1use std::{
2    borrow::Cow,
3    fmt,
4    ops::{Deref, DerefMut},
5};
6
7use crate::{format::*, *};
8
9use paste::paste;
10
11/// Wrapper around attributes that are common to all elements
12///
13/// Since many elements don't have any of these attributes, this
14/// wrapper keeps the size of the element structs small.
15///
16/// `Deref`s (and `DerefMut`s) to [`GlobalAttributesInner`]
17#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
18pub struct GlobalAttributes<'a>(Option<Box<GlobalAttributesInner<'a>>>);
19
20impl<'a> GlobalAttributes<'a> {
21    /// No attributes
22    pub const EMPTY: Self = Self(None);
23}
24
25/// Attributes that are common to all elements
26///
27/// Wrapped by [`GlobalAttributes`]
28#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
29pub struct GlobalAttributesInner<'a> {
30    /// The `id` attribute
31    pub id: Cow<'a, str>,
32    /// The `class` attribute
33    pub class: Cow<'a, str>,
34    /// The `style` attribute
35    pub style: Cow<'a, str>,
36    /// The `title` attribute
37    pub title: Cow<'a, str>,
38    /// The `autofocus` attribute
39    pub autofocus: bool,
40    /// The `itemscope` attribute
41    pub itemscope: bool,
42}
43
44pub(crate) static DEFAULT_GLOBAL_ATTRIBUTES_INNER: GlobalAttributesInner<'static> =
45    GlobalAttributesInner {
46        id: Cow::Borrowed(""),
47        class: Cow::Borrowed(""),
48        style: Cow::Borrowed(""),
49        title: Cow::Borrowed(""),
50        autofocus: false,
51        itemscope: false,
52    };
53
54impl<'a> Deref for GlobalAttributes<'a> {
55    type Target = GlobalAttributesInner<'a>;
56    fn deref(&self) -> &Self::Target {
57        self.0
58            .as_deref()
59            .unwrap_or(&DEFAULT_GLOBAL_ATTRIBUTES_INNER)
60    }
61}
62
63impl<'a> DerefMut for GlobalAttributes<'a> {
64    fn deref_mut(&mut self) -> &mut Self::Target {
65        self.0.get_or_insert_with(std::default::Default::default)
66    }
67}
68
69impl<'a> IndentFormat for GlobalAttributes<'a> {
70    fn indent_fmt(&self, f: &mut IndentFormatter) -> fmt::Result {
71        id_write(&self.id, f.f)?;
72        class_write(&self.class, f.f)?;
73        style_write(&self.style, f.f)?;
74        title_write(&self.title, f.f)?;
75        autofocus_write(&self.autofocus, f.f)?;
76        itemscope_write(&self.itemscope, f.f)?;
77        Ok(())
78    }
79}
80
81macro_rules! attribute_struct {
82    ($name:tt[bool]) => {
83        paste! {
84            #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
85            #[doc = "The `"]
86            #[doc = stringify!($name)]
87            #[doc = "` attribute"]
88            pub struct [<$name:camel>];
89            #[allow(non_camel_case_types)]
90            pub(crate) type [<$name _t>]<'a> = bool;
91            #[allow(non_camel_case_types)]
92            pub(crate) type [<$name _ref_t>] = bool;
93            #[allow(non_snake_case)]
94            pub(crate) fn [<$name _take_ref>](val: &[<$name _t>]) -> [<$name _ref_t>] {
95                *val
96            }
97            #[allow(non_snake_case)]
98            pub(crate) fn [<$name _write>](b: &bool, f: &mut fmt::Formatter) -> fmt::Result {
99                if *b {
100                    write!(f, " {}", stringify!($name).trim_end_matches('_'))
101                } else {
102                    Ok(())
103                }
104            }
105            impl [<$name:camel>] {
106                fn take(self) -> bool {
107                    true
108                }
109            }
110        }
111    };
112    ($name:tt) => {
113        paste! {
114            #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
115            #[allow(non_camel_case_types)]
116            #[doc = "The `"]
117            #[doc = stringify!($name)]
118            #[doc = "` attribute"]
119            pub struct [<$name:camel>]<T = String>(pub T);
120            #[allow(non_camel_case_types)]
121            pub(crate) type [<$name _t>]<'a> = Cow<'a, str>;
122            #[allow(non_camel_case_types)]
123            pub(crate) type [<$name _ref_t>]<'a> = &'a str;
124            #[allow(non_snake_case)]
125            pub(crate) fn [<$name _take_ref>]<'a>(val: &'a [<$name _t>]) -> [<$name _ref_t>]<'a> {
126                val
127            }
128            #[allow(non_snake_case)]
129            pub(crate) fn [<$name _write>](s: &str, f: &mut fmt::Formatter) -> fmt::Result {
130                if s.is_empty() {
131                    Ok(())
132                } else {
133                    write!(f, " {}=\"{}\"", stringify!($name).trim_end_matches('_'), s)
134                }
135            }
136            impl<T> [<$name:camel>]<T> {
137                fn take(self) -> T {
138                    self.0
139                }
140            }
141        }
142    };
143}
144
145macro_rules! attribute_trait {
146    ($name:tt [bool]) => {
147        paste! {
148            impl<'a, E> ElementData<E> for [<$name:camel>]
149            where
150                E: [<Has $name:camel>]<'a>
151            {
152                fn add_to(self, element: &mut E) {
153                    element.[<set_ $name>](self.take());
154                }
155            }
156        }
157    };
158    ($name:tt) => {
159        paste! {
160            impl<'a, E, T> ElementData<E> for [<$name:camel>]<T>
161            where
162                E: [<Has $name:camel>]<'a>,
163                T: Into<Cow<'a, str>>,
164            {
165                fn add_to(self, element: &mut E) {
166                    element.[<set_ $name>](self.take());
167                }
168            }
169        }
170    };
171}
172
173macro_rules! attributes {
174    ($($name:tt $([$ty:ident])?),* $(,)?) => {
175        $(attribute_struct!($name $([$ty])*);)*
176        pub mod attribute_traits {
177            //! Traits that mark elements as having attributes
178            use super::*;
179            $(
180                paste! {
181                    #[doc = "Trait for elements that have the `"]
182                    #[doc = stringify!($name)]
183                    #[doc = "` attribute"]
184                    #[allow(non_camel_case_types)]
185                    pub trait [<Has $name:camel>]<'a> {
186                        #[doc = "Get the value of the `"]
187                        #[doc = stringify!($name)]
188                        #[doc = "` attribute"]
189                        fn [<get_ $name>](&self) -> [<$name _ref_t>];
190                        #[doc = "Set the value of the `"]
191                        #[doc = stringify!($name)]
192                        #[doc = "` attribute"]
193                        fn [<set_ $name>](&mut self, value: impl Into<[<$name _t>]<'a>>);
194                    }
195                }
196                attribute_trait!($name $([$ty])*);
197            )*
198        }
199    };
200}
201
202attributes!(
203    accept,
204    action,
205    align,
206    allow,
207    alt,
208    autocomplete,
209    autofocus[bool],
210    autoplay[bool],
211    charset,
212    checked[bool],
213    cite,
214    class,
215    clear,
216    color,
217    cols,
218    colspan,
219    command,
220    content,
221    controls[bool],
222    coords,
223    crossorigin,
224    data,
225    datetime,
226    decoding,
227    default[bool],
228    defer[bool],
229    dir,
230    dirname,
231    disabled[bool],
232    download,
233    enctype,
234    form,
235    formaction,
236    formenctype,
237    formmethod,
238    formnovalidate[bool],
239    formtarget,
240    headers,
241    height,
242    high,
243    href,
244    hreflang,
245    http_equiv,
246    icon,
247    id,
248    importance,
249    integrity,
250    intrinsicsize,
251    ismap[bool],
252    itemscope[bool],
253    kind,
254    label,
255    list,
256    loading,
257    low,
258    manifest,
259    max_length,
260    max,
261    maxlength,
262    media,
263    method,
264    min_length,
265    min,
266    minlength,
267    multiple[bool],
268    muted[bool],
269    name,
270    nomodule[bool],
271    nonce,
272    noshade,
273    novalidate[bool],
274    open[bool],
275    optimum,
276    pattern,
277    ping,
278    placeholder,
279    playsinline,
280    poster,
281    preload,
282    profile,
283    async[bool],
284    for,
285    loop[bool],
286    type,
287    radiogroup,
288    readonly[bool],
289    referrerpolicy,
290    rel,
291    required[bool],
292    reversed[bool],
293    rows,
294    rowspan,
295    sandbox,
296    scope,
297    selected[bool],
298    shape,
299    size,
300    sizes,
301    span,
302    src,
303    srcdoc,
304    srclang,
305    srcset,
306    start,
307    step,
308    style,
309    target,
310    title,
311    usemap,
312    value,
313    width,
314    wrap,
315    xmlns,
316);
317
318macro_rules! event {
319    ($($name:ident),* $(,)?) => {
320        /// Types of event handlers
321        ///
322        /// Use with [`On`] to add an event handler to an element
323        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
324        #[allow(missing_docs)]
325        pub enum Event {
326            $($name,)*
327        }
328
329        impl fmt::Display for Event {
330            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331                match self {
332                    $(Self::$name => write!(f, paste!(concat!("on", stringify!([<$name:lower>])))),)*
333                }
334            }
335        }
336    };
337}
338
339event!(
340    Abort,
341    AfterPrint,
342    BeforePrint,
343    BeforeUnload,
344    Blur,
345    CanPlay,
346    CanPlayThrough,
347    Change,
348    Click,
349    ContextMenu,
350    Copy,
351    CueChange,
352    Cut,
353    DblClick,
354    Drag,
355    DragEnd,
356    DragEnter,
357    DragLeave,
358    DragOver,
359    DragStart,
360    Drop,
361    DurationChange,
362    Emptied,
363    Ended,
364    Error,
365    Focus,
366    HashChange,
367    Input,
368    Invalid,
369    KeyDown,
370    KeyPress,
371    KeyUp,
372    Load,
373    LoadedData,
374    LoadedMetadata,
375    LoadStart,
376    Message,
377    MouseDown,
378    MouseMove,
379    MouseOut,
380    MouseOver,
381    MouseUp,
382    MouseWheel,
383    Offline,
384    Online,
385    PageHide,
386    PageShow,
387    Paste,
388    Pause,
389    Play,
390    Playing,
391    PopState,
392    Progress,
393    RateChange,
394    Reset,
395    Resize,
396    Scroll,
397    Search,
398    Seeked,
399    Seeking,
400    Select,
401    Stalled,
402    Storage,
403    Submit,
404    Suspend,
405    TimeUpdate,
406    Toggle,
407    Unload,
408    VolumeChange,
409    Waiting,
410    Wheel,
411);
412
413/// The HTML events
414#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
415pub struct Events<'a>(Vec<(Event, Cow<'a, str>)>);
416
417impl<'a> Events<'a> {
418    /// No events
419    pub const NONE: Self = Self(Vec::new());
420    /// Check if the events is empty
421    pub fn is_empty(&self) -> bool {
422        self.0.is_empty()
423    }
424    /// Check if the events contains an event
425    pub fn contains(&self, event: Event) -> bool {
426        self.0.iter().any(|(e, _)| e == &event)
427    }
428    /// Get the value of the event
429    pub fn get(&self, event: Event) -> Option<&str> {
430        self.0
431            .iter()
432            .find(|(e, _)| e == &event)
433            .map(|(_, v)| v.as_ref())
434    }
435    /// Insert an event and value
436    pub fn insert(&mut self, event: Event, value: impl Into<Cow<'a, str>>) {
437        if let Some(i) = self.0.iter().position(|(e, _)| e == &event) {
438            self.0[i].1 = value.into();
439        } else {
440            self.0.push((event, value.into()));
441        }
442    }
443    /// Remove the event
444    pub fn remove(&mut self, event: Event) {
445        self.0.retain(|(e, _)| e != &event);
446    }
447    /// Iterate over the events
448    pub fn iter(&self) -> impl Iterator<Item = (Event, &str)> {
449        self.0.iter().map(|(n, v)| (*n, v.as_ref()))
450    }
451}
452
453/// Add an event handler to an element
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
455pub struct On<V>(
456    /// The event
457    pub Event,
458    /// The value
459    pub V,
460);
461
462impl<'a, E, V> ElementData<E> for On<V>
463where
464    E: Element<'a>,
465    V: Into<Cow<'a, str>>,
466{
467    fn add_to(self, element: &mut E) {
468        element.events_mut().insert(self.0, self.1);
469    }
470}