sycamore_web/node/
mod.rs

1//! Implementation of rendering backend.
2
3use std::fmt;
4use std::num::NonZeroU32;
5
6use crate::*;
7
8cfg_not_ssr_item!(
9    mod dom_node;
10);
11cfg_not_ssr_item!(
12    #[cfg(feature = "hydrate")]
13    mod hydrate_node;
14);
15cfg_ssr_item!(
16    mod ssr_node;
17);
18mod dom_render;
19mod ssr_render;
20
21// We add this so that we get IDE support in Rust Analyzer.
22#[cfg(rust_analyzer)]
23mod dom_node;
24#[cfg(rust_analyzer)]
25mod hydrate_node;
26
27#[cfg_not_ssr]
28pub use dom_node::*;
29pub use dom_render::*;
30#[cfg_not_ssr]
31#[cfg(feature = "hydrate")]
32pub use hydrate_node::*;
33#[cfg_ssr]
34pub use ssr_node::*;
35pub use ssr_render::*;
36
37/// A trait that should be implemented for anything that represents an HTML node.
38pub trait ViewHtmlNode: ViewNode {
39    /// Create a new HTML element.
40    fn create_element(tag: Cow<'static, str>) -> Self;
41    /// Create a new HTML element with a XML namespace.
42    fn create_element_ns(namespace: &'static str, tag: Cow<'static, str>) -> Self;
43    /// Create a new HTML text node.
44    fn create_text_node(text: Cow<'static, str>) -> Self;
45    /// Create a new HTML text node whose value will be changed dynamically.
46    fn create_dynamic_text_node(text: Cow<'static, str>) -> Self {
47        Self::create_text_node(text)
48    }
49    /// Create a new HTML marker (comment) node.
50    fn create_marker_node() -> Self;
51
52    /// Set an HTML attribute.
53    fn set_attribute(&mut self, name: Cow<'static, str>, value: StringAttribute);
54    /// Set a boolean HTML attribute.
55    fn set_bool_attribute(&mut self, name: Cow<'static, str>, value: BoolAttribute);
56    /// Set a JS property on an element.
57    fn set_property(&mut self, name: Cow<'static, str>, value: MaybeDyn<JsValue>);
58    /// Set an event handler on an element.
59    fn set_event_handler(
60        &mut self,
61        name: Cow<'static, str>,
62        handler: impl FnMut(web_sys::Event) + 'static,
63    );
64    /// Set the inner HTML value of an element.
65    fn set_inner_html(&mut self, inner_html: Cow<'static, str>);
66
67    /// Return the raw web-sys node.
68    fn as_web_sys(&self) -> &web_sys::Node;
69    /// Wrap a raw web-sys node.
70    fn from_web_sys(node: web_sys::Node) -> Self;
71}
72
73/// A trait for unwrapping a type into an `HtmlNode`.
74pub trait AsHtmlNode {
75    fn as_html_node(&mut self) -> &mut HtmlNode;
76}
77
78thread_local! {
79    /// Whether we are in hydration mode or not.
80    pub(crate) static IS_HYDRATING: Cell<bool> = const { Cell::new(false) };
81}
82
83/// Returns whether we are currently hydrating or not.
84pub fn is_hydrating() -> bool {
85    IS_HYDRATING.with(Cell::get)
86}
87
88/// A struct for keeping track of state used for hydration.
89#[derive(Debug, Clone, Copy)]
90pub(crate) struct HydrationRegistry {
91    next_key: Signal<HydrationKey>,
92}
93
94// This is only used when hydrating.
95#[cfg_attr(not(feature = "hydrate"), allow(dead_code))]
96impl HydrationRegistry {
97    pub fn new() -> Self {
98        HydrationRegistry {
99            next_key: create_signal(HydrationKey {
100                suspense: 0,
101                element: 0,
102            }),
103        }
104    }
105
106    /// Get the next hydration key and increment the internal state. This new key will be unique.
107    pub fn next_key(self) -> HydrationKey {
108        let key = self.next_key.get_untracked();
109        self.next_key.set_silent(HydrationKey {
110            suspense: key.suspense,
111            element: key.element + 1,
112        });
113        key
114    }
115
116    /// Run the given function within a suspense scope.
117    ///
118    /// This sets the suspense key to the passed value and resets the element key to 0.
119    pub fn in_suspense_scope<T>(suspense: NonZeroU32, f: impl FnOnce() -> T) -> T {
120        let mut ret = None;
121        create_child_scope(|| {
122            provide_context(HydrationRegistry {
123                next_key: create_signal(HydrationKey {
124                    suspense: suspense.get(),
125                    element: 0,
126                }),
127            });
128            ret = Some(f());
129        });
130        ret.unwrap()
131    }
132}
133
134impl Default for HydrationRegistry {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
141pub struct HydrationKey {
142    /// Suspense key, or 0 if not in a suspense boundary.
143    pub suspense: u32,
144    /// Element key.
145    pub element: u32,
146}
147
148impl fmt::Display for HydrationKey {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        write!(f, "{}.{}", self.suspense, self.element)
151    }
152}
153
154impl HydrationKey {
155    pub fn parse(s: &str) -> Option<Self> {
156        let mut parts = s.split('.');
157        let suspense = parts.next()?.parse().ok()?;
158        let element = parts.next()?.parse().ok()?;
159        Some(HydrationKey { suspense, element })
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn display_hydration_key() {
169        let key = HydrationKey {
170            suspense: 1,
171            element: 2,
172        };
173        assert_eq!(key.to_string(), "1.2");
174    }
175
176    #[test]
177    fn parse_hydration_key() {
178        assert_eq!(
179            HydrationKey::parse("1.2"),
180            Some(HydrationKey {
181                suspense: 1,
182                element: 2
183            })
184        );
185        assert_eq!(HydrationKey::parse("1"), None);
186    }
187}