Skip to main content

fhtmx/
html_element.rs

1use crate::{
2    attribute::{AttributeValue, IntoAttributeValue},
3    element::{Element, set_attr, set_empty_attr},
4    node::{HtmlNode, IntoNode},
5};
6use indexmap::{IndexMap, IndexSet};
7use pastey::paste;
8use std::borrow::Cow;
9
10/// HTML void elements (self-closing tags).
11pub const VOID_ELEMENTS: &[&str] = &[
12    "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source",
13    "track", "wbr",
14];
15
16/// HTML inline elements.
17pub const INLINE_ELEMENTS: &[&str] = &[
18    "a", "abbr", "b", "bdo", "br", "button", "cite", "code", "em", "i", "img", "input", "kbd",
19    "label", "q", "s", "samp", "select", "small", "span", "strong", "sub", "sup", "textarea",
20    "time", "u", "var",
21];
22
23/// A standard HTML element.
24#[derive(Clone, Debug)]
25pub struct HtmlElement {
26    /// Tag name, e.g. `"div"`.
27    pub tag: &'static str,
28    /// Ordered map of attributes.
29    pub attrs: IndexMap<Cow<'static, str>, AttributeValue>,
30    /// Ordered set of CSS classes.
31    pub classes: IndexSet<Cow<'static, str>>,
32    /// Child nodes.
33    pub children: Vec<HtmlNode>,
34}
35
36impl HtmlElement {
37    /// Creates a new HTML element with the given tag name.
38    pub fn new(tag: &'static str) -> Self {
39        Self {
40            tag,
41            attrs: IndexMap::new(),
42            classes: IndexSet::new(),
43            children: Vec::new(),
44        }
45    }
46}
47
48/// Converts a value into an [`HtmlElement`].
49pub trait IntoHtmlElement {
50    /// Transforms into an [`HtmlElement`].
51    fn into_element(self) -> HtmlElement;
52}
53
54impl IntoHtmlElement for HtmlElement {
55    #[inline]
56    fn into_element(self) -> HtmlElement {
57        self
58    }
59}
60
61impl Element for HtmlElement {
62    #[inline]
63    fn tag(&self) -> &'static str {
64        self.tag
65    }
66
67    #[inline]
68    fn attrs(&self) -> &IndexMap<Cow<'static, str>, AttributeValue> {
69        &self.attrs
70    }
71
72    #[inline]
73    fn attrs_mut(&mut self) -> &mut IndexMap<Cow<'static, str>, AttributeValue> {
74        &mut self.attrs
75    }
76
77    #[inline]
78    fn classes(&self) -> &IndexSet<Cow<'static, str>> {
79        &self.classes
80    }
81
82    #[inline]
83    fn classes_mut(&mut self) -> &mut IndexSet<Cow<'static, str>> {
84        &mut self.classes
85    }
86
87    #[inline]
88    fn children(&self) -> &[HtmlNode] {
89        &self.children
90    }
91
92    #[inline]
93    fn children_mut(&mut self) -> &mut Vec<HtmlNode> {
94        &mut self.children
95    }
96
97    #[inline]
98    fn is_void_tag(&self) -> bool {
99        VOID_ELEMENTS.contains(&self.tag())
100    }
101
102    #[inline]
103    fn is_inline_tag(&self) -> bool {
104        INLINE_ELEMENTS.contains(&self.tag())
105    }
106}
107
108impl<T: IntoHtmlElement> IntoNode for T {
109    fn into_node(self) -> HtmlNode {
110        HtmlNode::Element(self.into_element())
111    }
112}
113
114macro_rules! create_tag_fn {
115    ($name:ident) => {
116        paste! {
117            #[doc = "Creates a `" $name "` html element."]
118            pub fn $name() -> HtmlElement {
119                HtmlElement::new(stringify!($name))
120            }
121        }
122    };
123
124    ($name:ident; $eg:expr) => {
125        paste! {
126            #[doc = "Creates a `" $name "` html element.\n"$eg]
127            pub fn $name() -> HtmlElement {
128                HtmlElement::new(stringify!($name))
129            }
130        }
131    };
132
133    ($name:ident$(;$eg:expr)?, $($rest:ident$(;$eg_rest:expr)?),+ $(,)?) => {
134        create_tag_fn!($name$(;$eg)?);
135        create_tag_fn!($($rest$(;$eg_rest)?),+);
136    };
137}
138
139create_tag_fn!(
140    a; "Defines a hyperlink",
141    abbr; "Defines an abbreviation or an acronym",
142    address; "Defines contact information for the author/owner of a document",
143    area; "Defines an area inside an image map",
144    article; "Defines an article",
145    aside; "Defines content aside from the page content",
146    audio; "Defines embedded sound content",
147    b; "Defines bold text",
148    base; "Specifies the base URL/target for all relative URLs in a document",
149    bdi; "Isolates a part of text that might be formatted in a different direction from other text outside it",
150    bdo; "Overrides the current text direction",
151    blockquote; "Defines a section that is quoted from another source",
152    body; "Defines the document's body",
153    br; "Defines a single line break",
154    button; "Defines a clickable button",
155    canvas; "Used to draw graphics, on the fly, via scripting (usually JavaScript)",
156    caption; "Defines a table caption",
157    cite; "Defines the title of a work",
158    code; "Defines a piece of computer code",
159    col; "Specifies column properties for each column within a `<colgroup>` element",
160    colgroup; "Specifies a group of one or more columns in a table for formatting",
161    data; "Adds a machine-readable translation of a given content",
162    datalist; "Specifies a list of pre-defined options for input controls",
163    dd; "Defines a description/value of a term in a description list",
164    del; "Defines text that has been deleted from a document",
165    details; "Defines additional details that the user can view or hide",
166    dfn; "Specifies a term that is going to be defined within the content",
167    dialog; "Defines a dialog box or window",
168    div; "Defines a section in a document",
169    dl; "Defines a description list",
170    dt; "Defines a term/name in a description list",
171    em; "Defines emphasized text",
172    embed; "Defines a container for an external application",
173    fieldset; "Groups related elements in a form",
174    figcaption; "Defines a caption for a `<figure>` element",
175    figure; "Specifies self-contained content",
176    footer; "Defines a footer for a document or section",
177    form; "Defines an HTML form for user input",
178    h1; "Defines HTML headings",
179    h2; "Defines HTML headings",
180    h3; "Defines HTML headings",
181    h4; "Defines HTML headings",
182    h5; "Defines HTML headings",
183    h6; "Defines HTML headings",
184    head; "Contains metadata/information for the document",
185    header; "Defines a header for a document or section",
186    hgroup; "Defines a header and related content",
187    hr; "Defines a thematic change in the content",
188    html; "Defines the root of an HTML document",
189    i; "Defines a part of text in an alternate voice or mood",
190    iframe; "Defines an inline frame",
191    img; "Defines an image",
192    input; "Defines an input control",
193    ins; "Defines a text that has been inserted into a document",
194    kbd; "Defines keyboard input",
195    label; "Defines a label for an `<input>` element",
196    legend; "Defines a caption for a `<fieldset>` element",
197    li; "Defines a list item",
198    link; "Defines the relationship between a document and an external resource (most used to link to style sheets)",
199    map; "Defines an image map",
200    mark; "Defines marked/highlighted text",
201    menu; "Defines an unordered list",
202    meta; "Defines metadata about an HTML document",
203    meter; "Defines a scalar measurement within a known range (a gauge)",
204    nav; "Defines navigation links",
205    noscript; "Defines an alternate content for users that do not support client-side scripts",
206    object; "Defines a container for an external application",
207    ol; "Defines an ordered list",
208    optgroup; "Defines a group of related options in a drop-down list",
209    option; "Defines an option in a drop-down list",
210    output; "Defines the result of a calculation",
211    p; "Defines a paragraph",
212    param; "Defines a parameter for an object",
213    picture; "Defines a container for multiple image resources",
214    pre; "Defines preformatted text",
215    progress; "Represents the progress of a task",
216    q; "Defines a short quotation",
217    rp; "Defines what to show in browsers that do not support ruby annotations",
218    rt; "Defines an explanation/pronunciation of characters (for East Asian typography)",
219    ruby; "Defines a ruby annotation (for East Asian typography)",
220    s; "Defines text that is no longer correct",
221    samp; "Defines sample output from a computer program",
222    script; "Defines a client-side script",
223    search; "Defines a search section",
224    section; "Defines a section in a document",
225    select; "Defines a drop-down list",
226    small; "Defines smaller text",
227    source; "Defines multiple media resources for media elements (`<video>` and `<audio>`)",
228    span; "Defines a section in a document",
229    strong; "Defines important text",
230    style; "Defines style information for a document",
231    sub; "Defines subscripted text",
232    summary; "Defines a visible heading for a `<details>` element",
233    sup; "Defines superscripted text",
234    table; "Defines a table",
235    tbody; "Groups the body content in a table",
236    td; "Defines a cell in a table",
237    template; "Defines a container for content that should be hidden when the page loads",
238    textarea; "Defines a multiline input control (text area)",
239    tfoot; "Groups the footer content in a table",
240    th; "Defines a header cell in a table",
241    thead; "Groups the header content in a table",
242    time; "Defines a specific time (or datetime)",
243    title; "Defines a title for the document",
244    tr; "Defines a row in a table",
245    track; "Defines text tracks for media elements (`<video>` and `<audio>`)",
246    u; "Defines some text that is unarticulated and styled differently from normal text",
247    ul; "Defines an unordered list",
248    var; "Defines a variable",
249    video; "Defines embedded video content",
250    wbr; "Defines a possible line-break",
251);
252
253/// Creates a `main` html element.
254/// Specifies the main content of a document
255pub fn main_tag() -> HtmlElement {
256    HtmlElement::new("main")
257}
258
259impl HtmlElement {
260    set_attr!(
261        accesskey,
262        alt,
263        contenteditable,
264        decoding,
265        data_tip = "data-tip",
266        dir,
267        draggable,
268        enterkeyhint,
269        for_ = "for",
270        height,
271        href,
272        id,
273        inputmode,
274        lang,
275        loading,
276        max,
277        maxlength,
278        media,
279        min,
280        minlength,
281        name,
282        pattern,
283        placeholder,
284        rel,
285        role,
286        sizes,
287        spellcheck,
288        src,
289        srcset,
290        step,
291        style,
292        tabindex,
293        target,
294        title,
295        translate,
296        typ = "type",
297        value,
298        width
299    );
300
301    set_empty_attr!(
302        autofocus, blocking, checked, defer, disabled, hidden, inert, multiple, nomodule, open,
303        popover, r#async, readonly, required, selected
304    );
305
306    /// Selects all texts when the element is focused (eg: useful for a search field)
307    pub fn select_onfocus(self) -> Self {
308        self.set_attr("onfocus", "this.select()")
309    }
310}