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;