Skip to main content

tachys/html/
mod.rs

1use self::attribute::Attribute;
2use crate::{
3    hydration::Cursor,
4    no_attrs,
5    prelude::{AddAnyAttr, Mountable},
6    renderer::{
7        dom::{Element, Node},
8        CastFrom, Rndr,
9    },
10    view::{Position, PositionState, Render, RenderHtml},
11};
12use attribute::any_attribute::AnyAttribute;
13use std::borrow::Cow;
14
15/// Diagnostic message shared by event, directive, and property `.expect()` calls.
16///
17/// When the `ssr` feature is active, tachys skips creating client-side values
18/// (event handlers, directives, properties) to avoid `SendWrapper` cross-thread
19/// panics on multithreaded servers. If these `.expect()` calls fire, it means
20/// the `ssr` feature was activated unintentionally via Cargo feature
21/// unification in a client-side (CSR or hydrate) build.
22pub(crate) const FEATURE_CONFLICT_DIAGNOSTIC: &str =
23    "Value is None because the `ssr` feature is active. When `ssr` is \
24     enabled, tachys skips creating client-side values (event handlers, \
25     directives, properties) to avoid cross-thread panics on multithreaded \
26     servers. If you are building a client-side (CSR or hydrate) target, this \
27     means the `ssr` feature is being activated unintentionally via Cargo \
28     feature unification; another dependency in your workspace is enabling \
29     it. Run `cargo tree -e features -i tachys` to identify the source.";
30
31/// Types for HTML attributes.
32pub mod attribute;
33/// Types for manipulating the `class` attribute and `classList`.
34pub mod class;
35/// Types for creating user-defined attributes with custom behavior (directives).
36pub mod directive;
37/// Types for HTML elements.
38pub mod element;
39/// Types for DOM events.
40pub mod event;
41/// Types for adding interactive islands to inert HTML pages.
42pub mod islands;
43/// Types for accessing a reference to an HTML element.
44pub mod node_ref;
45/// Types for DOM properties.
46pub mod property;
47/// Types for the `style` attribute and individual style manipulation.
48pub mod style;
49
50/// A `<!DOCTYPE>` declaration.
51pub struct Doctype {
52    value: &'static str,
53}
54
55/// Creates a `<!DOCTYPE>`.
56pub fn doctype(value: &'static str) -> Doctype {
57    Doctype { value }
58}
59
60impl Render for Doctype {
61    type State = ();
62
63    fn build(self) -> Self::State {}
64
65    fn rebuild(self, _state: &mut Self::State) {}
66}
67
68no_attrs!(Doctype);
69
70impl RenderHtml for Doctype {
71    type AsyncOutput = Self;
72    type Owned = Self;
73
74    const MIN_LENGTH: usize = "<!DOCTYPE html>".len();
75
76    fn dry_resolve(&mut self) {}
77
78    async fn resolve(self) -> Self::AsyncOutput {
79        self
80    }
81
82    fn to_html_with_buf(
83        self,
84        buf: &mut String,
85        _position: &mut Position,
86        _escape: bool,
87        _mark_branches: bool,
88        _extra_attrs: Vec<AnyAttribute>,
89    ) {
90        buf.push_str("<!DOCTYPE ");
91        buf.push_str(self.value);
92        buf.push('>');
93    }
94
95    fn hydrate<const FROM_SERVER: bool>(
96        self,
97        _cursor: &Cursor,
98        _position: &PositionState,
99    ) -> Self::State {
100    }
101
102    fn into_owned(self) -> Self::Owned {
103        self
104    }
105}
106
107/// An element that contains no interactivity, and whose contents can be known at compile time.
108pub struct InertElement {
109    html: Cow<'static, str>,
110}
111
112impl InertElement {
113    /// Creates a new inert element.
114    pub fn new(html: impl Into<Cow<'static, str>>) -> Self {
115        Self { html: html.into() }
116    }
117}
118
119/// Retained view state for [`InertElement`].
120pub struct InertElementState(Cow<'static, str>, Element);
121
122impl Mountable for InertElementState {
123    fn unmount(&mut self) {
124        self.1.unmount();
125    }
126
127    fn mount(&mut self, parent: &Element, marker: Option<&Node>) {
128        self.1.mount(parent, marker)
129    }
130
131    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
132        self.1.insert_before_this(child)
133    }
134
135    fn elements(&self) -> Vec<crate::renderer::types::Element> {
136        vec![self.1.clone()]
137    }
138}
139
140impl Render for InertElement {
141    type State = InertElementState;
142
143    fn build(self) -> Self::State {
144        let el = Rndr::create_element_from_html(self.html.clone());
145        InertElementState(self.html, el)
146    }
147
148    fn rebuild(self, state: &mut Self::State) {
149        let InertElementState(prev, el) = state;
150        if &self.html != prev {
151            let mut new_el = Rndr::create_element_from_html(self.html.clone());
152            el.insert_before_this(&mut new_el);
153            el.unmount();
154            *el = new_el;
155            *prev = self.html;
156        }
157    }
158}
159
160impl AddAnyAttr for InertElement {
161    type Output<SomeNewAttr: Attribute> = Self;
162
163    fn add_any_attr<NewAttr: Attribute>(
164        self,
165        _attr: NewAttr,
166    ) -> Self::Output<NewAttr>
167    where
168        Self::Output<NewAttr>: RenderHtml,
169    {
170        panic!(
171            "InertElement does not support adding attributes. It should only \
172             be used as a child, and not returned at the top level."
173        )
174    }
175}
176
177impl RenderHtml for InertElement {
178    type AsyncOutput = Self;
179    type Owned = Self;
180
181    const MIN_LENGTH: usize = 0;
182
183    fn html_len(&self) -> usize {
184        self.html.len()
185    }
186
187    fn dry_resolve(&mut self) {}
188
189    async fn resolve(self) -> Self {
190        self
191    }
192
193    fn to_html_with_buf(
194        self,
195        buf: &mut String,
196        position: &mut Position,
197        _escape: bool,
198        _mark_branches: bool,
199        _extra_attrs: Vec<AnyAttribute>,
200    ) {
201        buf.push_str(&self.html);
202        *position = Position::NextChild;
203    }
204
205    fn hydrate<const FROM_SERVER: bool>(
206        self,
207        cursor: &Cursor,
208        position: &PositionState,
209    ) -> Self::State {
210        let curr_position = position.get();
211        if curr_position == Position::FirstChild {
212            cursor.child();
213        } else if curr_position != Position::Current {
214            cursor.sibling();
215        }
216        let el = crate::renderer::types::Element::cast_from(cursor.current())
217            .unwrap();
218        position.set(Position::NextChild);
219        InertElementState(self.html, el)
220    }
221
222    fn into_owned(self) -> Self::Owned {
223        self
224    }
225}