Skip to main content

euv_core/vdom/node/
struct.rs

1use super::*;
2
3/// Inner storage for a dynamic node render closure.
4///
5/// Boxes a `dyn FnMut(&mut HookContext) -> VirtualNode` so it can be stored behind `Rc<UnsafeCell<>>`.
6#[derive(CustomDebug, Data, New)]
7pub(crate) struct RenderFnInner {
8    /// The boxed render closure.
9    #[debug(skip)]
10    #[get(pub(crate))]
11    #[get_mut(pub(crate))]
12    #[set(pub(crate))]
13    pub(crate) render_fn: Box<dyn FnMut(&mut HookContext) -> VirtualNode>,
14}
15
16/// Represents a text node in the virtual DOM.
17///
18/// Text nodes may optionally be bound to a reactive signal for automatic updates.
19///
20/// OPT 29: `content` is `Cow<'static, str>` instead of `String` so the
21/// `html!` macro can emit `Cow::Borrowed("...")` for literal text
22/// without allocating a `String` per text node per render. Runtime
23/// text (signals, `format!`, `to_string()`) still falls through to
24/// `Cow::Owned`. The renderer's `set_text_content` and
25/// `create_text_node` calls take `&str`, which is what `Cow<'static, str>`
26/// derefs to, so call sites use `.as_ref()` (yields `&str`).
27#[derive(Clone, CustomDebug, Data, New)]
28pub struct TextNode {
29    /// The text content.
30    #[get(pub(crate))]
31    #[get_mut(pub(crate))]
32    #[set(pub(crate))]
33    #[get_mut(pub(crate))]
34    #[set(pub(crate))]
35    pub(crate) content: Cow<'static, str>,
36    /// An optional binder that wires a freshly created DOM `Text` node to
37    /// its backing signal. The binder is invoked once per DOM text node at
38    /// materialization time (`create_dom_with_doc`); it subscribes the
39    /// source signal directly to that node, so no intermediate bridge
40    /// signal is allocated per binding. Kept text nodes are patched in
41    /// place without re-invoking the binder, so re-renders of a bound
42    /// position never accumulate subscriptions.
43    #[debug(skip)]
44    #[get(pub(crate))]
45    #[get_mut(pub(crate))]
46    #[set(pub(crate))]
47    #[get(pub(crate))]
48    #[get_mut(pub(crate))]
49    #[set(pub(crate))]
50    pub(crate) binder: Option<TextNodeBinder>,
51}
52
53/// A closure-based dynamic node that re-renders when its dependency signals change.
54///
55/// Holds a shared reference to a heap-allocated render closure that produces a fresh
56/// `VirtualNode` on each evaluation. The renderer subscribes to the closure's
57/// signals and patches the DOM automatically.
58/// Contains a `HookContext` that persists hook state (like `use_signal`) across
59/// re-renders, ensuring that signal values are not reset when the render function
60/// is called again.
61///
62/// Uses `Rc<UnsafeCell<>>` instead of `Rc<RefCell<>>` to avoid runtime borrow
63/// checking overhead. Safety is guaranteed by the single-threaded WASM context.
64/// The `Rc` provides automatic memory management — the render closure is freed
65/// when the last reference (either in the VirtualNode tree or the signal update
66/// callback) is dropped.
67#[derive(Clone, CustomDebug, Data, New)]
68#[new(pub(crate))]
69pub struct DynamicNode {
70    /// Shared reference to the heap-allocated render closure inner state.
71    /// `Rc` ensures automatic deallocation; `UnsafeCell` allows mutable access
72    /// without RefCell overhead.
73    #[debug(skip)]
74    #[get(pub(crate))]
75    #[get_mut(pub(crate))]
76    #[set(pub(crate))]
77    #[get(pub(crate))]
78    #[get_mut(pub(crate))]
79    #[set(pub(crate))]
80    pub(crate) render_fn: Rc<UnsafeCell<RenderFnInner>>,
81    /// Persistent hook context for this dynamic node, storing signal
82    /// state and other hook values across render cycles.
83    #[get(pub(crate))]
84    #[get_mut(pub(crate))]
85    #[set(pub(crate))]
86    #[get(pub(crate))]
87    #[get_mut(pub(crate))]
88    #[set(pub(crate))]
89    pub(crate) hook_context: HookContext,
90}