Skip to main content

leptos_meta/
html.rs

1use crate::ServerMetaContext;
2use leptos::{
3    attr::{any_attribute::AnyAttribute, NextAttribute},
4    component, html,
5    reactive::owner::use_context,
6    tachys::{
7        dom::document,
8        html::attribute::Attribute,
9        hydration::Cursor,
10        view::{
11            add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
12            RenderHtml,
13        },
14    },
15    IntoView,
16};
17
18/// A component to set metadata on the document’s `<html>` element from
19/// within the application.
20///
21/// This component takes no props, but can take any number of spread attributes
22/// following the `{..}` operator.
23///
24/// ```
25/// use leptos::prelude::*;
26/// use leptos_meta::*;
27///
28/// #[component]
29/// fn MyApp() -> impl IntoView {
30///     provide_meta_context();
31///
32///     view! {
33///       <main>
34///         <Html
35///           {..}
36///           lang="he"
37///           dir="rtl"
38///           data-theme="dark"
39///         />
40///       </main>
41///     }
42/// }
43/// ```
44#[component]
45pub fn Html() -> impl IntoView {
46    HtmlView { attributes: () }
47}
48
49struct HtmlView<At> {
50    attributes: At,
51}
52
53struct HtmlViewState<At>
54where
55    At: Attribute,
56{
57    attributes: At::State,
58}
59
60impl<At> Render for HtmlView<At>
61where
62    At: Attribute,
63{
64    type State = HtmlViewState<At>;
65
66    fn build(self) -> Self::State {
67        let el = document()
68            .document_element()
69            .expect("there to be a <html> element");
70
71        let attributes = self.attributes.build(&el);
72
73        HtmlViewState { attributes }
74    }
75
76    fn rebuild(self, state: &mut Self::State) {
77        self.attributes.rebuild(&mut state.attributes);
78    }
79}
80
81impl<At> AddAnyAttr for HtmlView<At>
82where
83    At: Attribute,
84{
85    type Output<SomeNewAttr: Attribute> =
86        HtmlView<<At as NextAttribute>::Output<SomeNewAttr>>;
87
88    fn add_any_attr<NewAttr: Attribute>(
89        self,
90        attr: NewAttr,
91    ) -> Self::Output<NewAttr>
92    where
93        Self::Output<NewAttr>: RenderHtml,
94    {
95        HtmlView {
96            attributes: self.attributes.add_any_attr(attr),
97        }
98    }
99}
100
101impl<At> RenderHtml for HtmlView<At>
102where
103    At: Attribute,
104{
105    type AsyncOutput = HtmlView<At::AsyncOutput>;
106    type Owned = HtmlView<At::CloneableOwned>;
107
108    const MIN_LENGTH: usize = At::MIN_LENGTH;
109
110    fn dry_resolve(&mut self) {
111        self.attributes.dry_resolve();
112    }
113
114    async fn resolve(self) -> Self::AsyncOutput {
115        HtmlView {
116            attributes: self.attributes.resolve().await,
117        }
118    }
119
120    fn to_html_with_buf(
121        self,
122        _buf: &mut String,
123        _position: &mut Position,
124        _escape: bool,
125        _mark_branches: bool,
126        extra_attrs: Vec<AnyAttribute>,
127    ) {
128        if let Some(meta) = use_context::<ServerMetaContext>() {
129            let mut buf = String::new();
130            _ = html::attributes_to_html(
131                (self.attributes, extra_attrs),
132                &mut buf,
133            );
134            if !buf.is_empty() {
135                _ = meta.html.send(buf);
136            }
137        }
138    }
139
140    fn hydrate<const FROM_SERVER: bool>(
141        self,
142        _cursor: &Cursor,
143        _position: &PositionState,
144    ) -> Self::State {
145        let el = document()
146            .document_element()
147            .expect("there to be a <html> element");
148
149        let attributes = self.attributes.hydrate::<FROM_SERVER>(&el);
150
151        HtmlViewState { attributes }
152    }
153
154    fn into_owned(self) -> Self::Owned {
155        HtmlView {
156            attributes: self.attributes.into_cloneable_owned(),
157        }
158    }
159}
160
161impl<At> Mountable for HtmlViewState<At>
162where
163    At: Attribute,
164{
165    fn unmount(&mut self) {}
166
167    fn mount(
168        &mut self,
169        _parent: &leptos::tachys::renderer::types::Element,
170        _marker: Option<&leptos::tachys::renderer::types::Node>,
171    ) {
172        // <Html> only sets attributes
173        // the <html> tag doesn't need to be mounted anywhere, of course
174    }
175
176    fn insert_before_this(&self, _child: &mut dyn Mountable) -> bool {
177        false
178    }
179
180    fn elements(&self) -> Vec<leptos::tachys::renderer::types::Element> {
181        vec![document()
182            .document_element()
183            .expect("there to be a <html> element")]
184    }
185}