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
10pub const VOID_ELEMENTS: &[&str] = &[
12 "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source",
13 "track", "wbr",
14];
15
16pub 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#[derive(Clone, Debug)]
25pub struct HtmlElement {
26 pub tag: &'static str,
28 pub attrs: IndexMap<Cow<'static, str>, AttributeValue>,
30 pub classes: IndexSet<Cow<'static, str>>,
32 pub children: Vec<HtmlNode>,
34}
35
36impl HtmlElement {
37 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
48pub trait IntoHtmlElement {
50 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
253pub 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 pub fn select_onfocus(self) -> Self {
308 self.set_attr("onfocus", "this.select()")
309 }
310}