html_node/typed.rs
1//! Typed HTML nodes.
2//!
3//! # Examples
4//!
5//! ```rust
6//! use html_node::typed::{self, elements::*};
7//! // ^^^^^^^^^^^
8//! // required to bring type definitions
9//! // of all basic html elements into
10//! // the current scope.
11//! // (can also use `elements::div`, etc.)
12//!
13//! #[derive(Clone, Debug)]
14//! struct Location {
15//! x: i32,
16//! y: i32,
17//! }
18//!
19//! impl std::fmt::Display for Location {
20//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21//! write!(f, "{},{}", self.x, self.y)
22//! }
23//! }
24//!
25//! // defines a custom element named `CustomElement`, with the specified attributes.
26//! // underscores in attributes get converted to and from hyphens in the
27//! // `typed::html!` macro and rendering.
28//!
29//! // note that global attributes like `id` will be pre-defined when
30//! // using the `typed::element!` macro.
31//!
32//! typed::element! {
33//! CustomElement("custom-element") {
34//! custom_attr, // implictly typed as a `String`
35//! location: Location,
36//! }
37//! }
38//!
39//! typed::attributes! {
40//! [TestAttrs] {
41//! test_val: i32,
42//! }
43//! }
44//!
45//! // creates a normal `Node`, but checks types at compile-time!
46//! let html = typed::html! { (test: TestAttrs, any)
47//! // ^^^^^^^^^^^^^^^^^^^^^^ these are extension attributes.
48//! // they are not required, but allow you to specify extra attributes
49//! // which will be available within this macro invocation.
50//! // those of the form `attr-prefix: Type` will be type checked, and
51//! // those with just `attr-prefix` will be considered "catch-all" prefixes
52//! // allowing any attribute with that prefix to be specified.
53//! // `data-*` and `aria-*` are predefined as catch-all prefixes.
54//! <div id="container">
55//! <CustomElement test-val=42 any-whatever data-cool=true id="el" custom-attr="test" location=Location { x: 1, y: 2 } />
56//! </div>
57//! };
58//!
59//! assert_eq!(
60//! html.to_string(),
61//! "\
62//! <div id=\"container\">\
63//! <custom-element id=\"el\" custom-attr=\"test\" location=\"1,2\" test-val=\"42\" any-whatever data-cool=\"true\">\
64//! </custom-element>\
65//! </div>\
66//! ",
67//! );
68
69#[allow(clippy::module_name_repetitions)]
70pub use html_node_core::typed::{elements, Attribute, TypedAttributes, TypedElement};
71/// Make a typed set of HTML attributes.
72///
73/// Used internally by [`element!`].
74pub use html_node_core::typed_attributes as attributes;
75/// Make a typed HTML node.
76///
77/// # Examples
78///
79/// ## Passing Type-Checking
80///
81/// ```rust
82/// use html_node::typed::{self, elements::*};
83///
84/// typed::component! {
85/// CustomBody {
86/// r: u8,
87/// g: u8,
88/// b: u8,
89/// width: i32,
90/// };
91///
92/// |CustomBodyAttributes { r, g, b, width }, _, children| typed::html! {
93/// <div style={format!("background-color: rgb({r}, {g}, {b}); width: {width}px;")}>
94/// { children }
95/// </div>
96/// }
97/// }
98///
99/// let html = typed::html! {
100/// <CustomBody component r=255 g=0 b=0 width=100>"Hello, world!"</CustomBody>
101/// };
102///
103/// let expected = "\
104/// <div style=\"background-color: rgb(255, 0, 0); width: 100px;\">\
105/// Hello, world!\
106/// </div>\
107/// ";
108///
109/// assert_eq!(html.to_string(), expected);
110/// ```
111pub use html_node_core::typed_component as component;
112/// Make a typed element.
113///
114/// # Examples
115///
116/// ## Fully Generated (With Custom Name)
117///
118/// ```rust
119/// use html_node::typed;
120///
121/// typed::element! {
122/// CustomElement("custom-element") {
123/// custom_attr,
124/// }
125/// }
126///
127/// // note that global attributes like `id` will be pre-defined when
128/// // using the `typed::element!` macro.
129/// assert_eq!(
130/// typed::html!(<CustomElement id="el" custom-attr="test" />).to_string(),
131/// r#"<custom-element id="el" custom-attr="test"></custom-element>"#,
132/// );
133/// ```
134///
135/// ## Fully Generated (With Default Name)
136///
137/// ```rust
138/// use html_node::typed;
139///
140/// typed::element! {
141/// CustomElement {
142/// custom_attr,
143/// }
144/// }
145///
146/// assert_eq!(
147/// typed::html!(<CustomElement id="el" custom-attr="test" />).to_string(),
148/// r#"<CustomElement id="el" custom-attr="test"></CustomElement>"#,
149/// );
150/// ```
151///
152/// ## Generated With Custom Attributes Name
153///
154/// ```rust
155/// use html_node::typed::{self, TypedAttributes};
156///
157/// typed::element! {
158/// CustomElement [CustomElementAttributesDifferent] {
159/// custom_attr,
160/// }
161/// }
162///
163/// assert_eq!(
164/// typed::html!(<CustomElement id="el" custom-attr="test" />).to_string(),
165/// r#"<CustomElement id="el" custom-attr="test"></CustomElement>"#,
166/// );
167/// ```
168///
169/// ## Generated With Custom Attributes
170///
171/// ```rust
172/// use html_node::typed::{self, Attribute, TypedAttributes};
173///
174/// #[derive(Debug, Clone, Default)]
175/// struct CustomElementAttributes {
176/// custom_attr: Attribute<String>,
177/// }
178///
179/// impl TypedAttributes for CustomElementAttributes {
180/// fn into_attributes(self) -> Vec<(String, Option<String>)> {
181/// vec![self.custom_attr.into_option().map(|v| ("custom-attr".into(), v))]
182/// .into_iter()
183/// .flatten()
184/// .collect()
185/// }
186/// }
187///
188/// typed::element! {
189/// CustomElement [CustomElementAttributes]
190/// }
191///
192/// // note that global attributes like `id` will not be allowed here
193/// // because they are not defined in `CustomElementAttributes`.
194/// assert_eq!(
195/// typed::html!(<CustomElement custom-attr="test" />).to_string(),
196/// r#"<CustomElement custom-attr="test"></CustomElement>"#,
197/// );
198/// ```
199pub use html_node_core::typed_element as element;
200/// Make many typed elements.
201///
202/// This uses the same syntax as [`element!`], but repeated and seperated
203/// by semicolons (`;`).
204pub use html_node_core::typed_elements as elements;
205/// Make a typed HTML node.
206///
207/// # Examples
208///
209/// ## Passing Type-Checking
210///
211/// ```rust
212/// use html_node::typed::{self, elements::*};
213///
214/// let html = typed::html! {
215/// <div class="cool" id="hello-world" data-my-attr="hello" aria-label="world">
216/// "Hello, world!"
217/// </div>
218/// };
219///
220/// let expected = "\
221/// <div class=\"cool\" id=\"hello-world\" data-my-attr=\"hello\" aria-label=\"world\">\
222/// Hello, world!\
223/// </div>\
224/// ";
225///
226/// assert_eq!(html.to_string(), expected);
227/// ```
228///
229/// ## Failing Type-Checking
230///
231/// ```compile_fail
232/// use html_node::typed::{self, elements::*};
233///
234/// let html = typed::html! {
235/// // ERROR: struct `html_node::typed::elements::DivAttributes` has no field named `my_attr`
236/// <div class="cool" id="hello-world" my-attr="hello">
237/// {text!("Hello, world!")}
238/// </div>
239/// };
240/// ```
241pub use html_node_macro::typed_html as html;