Skip to main content

euv_core/noderef/
impl.rs

1use super::*;
2
3impl<T: ?Sized> NodeRef<T> {
4    /// Creates a new empty `NodeRef`.
5    ///
6    /// This constructor is `pub` so that `Default::default()` and
7    /// `App::use_node_ref()` can both produce handles. Callers should not
8    /// normally need to invoke this directly — use [`App::use_node_ref`]
9    /// inside a component so the handle participates in the hook order.
10    pub fn new() -> Self {
11        Self::default()
12    }
13
14    /// Returns a clone of the raw `JsValue` if an element is currently
15    /// attached, otherwise `None`.
16    ///
17    /// Use this when you only need the underlying DOM element without
18    /// caring about its concrete type (e.g., passing it to a third-party
19    /// JS interop function). For type-safe access, use [`get_cloned`].
20    ///
21    /// [`get_cloned`]: NodeRef::get_cloned
22    ///
23    /// # Returns
24    ///
25    /// - `Option<JsValue>` - The current value (or a snapshot thereof).
26    pub fn get(&self) -> Option<JsValue> {
27        // SAFETY: we never hand out `&mut Option<JsValue>`; the only mutating
28        // access goes through `set` / `clear`, both of which `take` the
29        // existing value first, so there is no aliasing on the inner `JsValue`.
30        let cell: *mut Option<JsValue> = self.inner.get();
31        unsafe { (*cell).as_ref().cloned() }
32    }
33
34    /// Returns a clone of the attached element cast to `T`, or `None` if
35    /// no element is attached or the cast fails.
36    ///
37    /// The cast uses [`JsCast::dyn_into`] and discards the `Err` arm — a
38    /// failed cast is reported as `None` rather than panicking, which
39    /// matches React/Yew behaviour and avoids crashing the renderer on
40    /// ref misuse.
41    ///
42    /// # Returns
43    ///
44    /// - `Option<T>` - A cloned copy of the inner value, if present.
45    pub fn get_cloned(&self) -> Option<T>
46    where
47        T: JsCast,
48    {
49        let value: JsValue = self.get()?;
50        value.dyn_into::<T>().ok()
51    }
52
53    /// Stores the given element as the current value of the handle.
54    ///
55    /// This is called by the renderer after a `ref:` attribute fires;
56    /// users should not normally need to call it directly. Setting the
57    /// value clears any previous element first — multiple mounts of the
58    /// same `NodeRef` therefore always reflect the most recent element.
59    ///
60    /// # Arguments
61    ///
62    /// - `JsValue` - A `JsValue` parameter.
63    pub fn set(&self, value: JsValue) {
64        // SAFETY: `set` replaces the inner value wholesale via `replace`
65        // (which uses `mem::swap` under the hood), so we never hold an
66        // overlapping reference. The previous `JsValue` is dropped before
67        // the new one is stored.
68        let cell: *mut Option<JsValue> = self.inner.get();
69        unsafe {
70            let _: Option<JsValue> = (*cell).replace(value);
71        }
72    }
73
74    /// Returns a shared clone of the interior cell for registry wiring.
75    ///
76    /// The renderer calls this when a `ref:` attribute fires so the
77    /// `NodeRef`'s cell can be registered under the element's `euv_id`
78    /// and cleared by `cleanup_subtree` when the element is unmounted.
79    ///
80    /// # Returns
81    ///
82    /// - `NodeRefEntry` - A clone of the shared interior cell.
83    pub(crate) fn share_cell(&self) -> NodeRefEntry {
84        self.inner.clone()
85    }
86
87    /// Clears the currently attached element, if any.
88    ///
89    /// Called by the renderer when a node is unmounted. After `clear`,
90    /// [`get`] and [`get_cloned`] both return `None` until the next
91    /// `set` call.
92    ///
93    /// [`get`]: NodeRef::get
94    pub fn clear(&self) {
95        let cell: *mut Option<JsValue> = self.inner.get();
96        unsafe {
97            let _: Option<JsValue> = (*cell).take();
98        }
99    }
100
101    /// Returns `true` if an element is currently attached to this handle.
102    ///
103    /// # Returns
104    ///
105    /// - `bool` - `true` when the value has been initialised.
106    pub fn is_set(&self) -> bool {
107        let cell: *const Option<JsValue> = self.inner.get();
108        // SAFETY: only `is_some()` is called — no `&mut`, no mutation.
109        unsafe { (*cell).is_some() }
110    }
111}
112
113// Blanket impl over the unsized `web_sys::Node` is what most users want,
114// but the macro passes a `JsValue` and the user chooses `T` per use site,
115// so we don't constrain `T` here — `get_cloned`'s `JsCast` bound is the
116// single point where the type check happens.
117//
118// `NodeRef<dyn Any>` (or any unsized type) is accepted by the type system
119// because `T: ?Sized`. The internal `JsValue` storage is independent of
120// `T` so there is no soundness concern.
121
122/// Manual `Clone` impl: `T` is `?Sized` so the `derive` macro (which
123/// requires `T: Clone`) cannot be used. `Rc` clone is cheap and shares
124/// the underlying cell with all clones.
125impl<T: ?Sized> Clone for NodeRef<T> {
126    /// Clones the [`NodeRef`] by reusing shared, cheap-to-clone state where possible.
127    fn clone(&self) -> Self {
128        Self {
129            inner: self.inner.clone(),
130            _marker: PhantomData,
131        }
132    }
133}
134
135impl<T: ?Sized> Default for NodeRef<T> {
136    /// Returns an empty `NodeRef` (no element associated).
137    ///
138    /// The returned handle is independent of any hook context: it is not
139    /// registered as a hook and will never be populated by the renderer
140    /// unless it is the same instance that was returned by
141    /// [`App::use_node_ref`] and then later attached via a `ref:` attribute.
142    /// Prefer [`App::use_node_ref`] inside a component for normal usage.
143    fn default() -> Self {
144        Self {
145            inner: Rc::new(UnsafeCell::new(None)),
146            _marker: PhantomData,
147        }
148    }
149}
150
151impl<T: ?Sized> Debug for NodeRef<T> {
152    /// Formats the [`NodeRef`] via the supplied formatter.
153    ///
154    /// # Arguments
155    ///
156    /// - `&mut Formatter<'_>` - The formatter receiving the formatted output.
157    ///
158    /// # Returns
159    ///
160    /// - `fmt::Result` - Result of the formatting operation.
161    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
162        formatter
163            .debug_struct("NodeRef")
164            .field("is_set", &self.is_set())
165            .finish()
166    }
167}