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 /// Clears the currently attached element, if any.
75 ///
76 /// Called by the renderer when a node is unmounted. After `clear`,
77 /// [`get`] and [`get_cloned`] both return `None` until the next
78 /// `set` call.
79 ///
80 /// [`get`]: NodeRef::get
81 pub fn clear(&self) {
82 let cell: *mut Option<JsValue> = self.inner.get();
83 unsafe {
84 let _: Option<JsValue> = (*cell).take();
85 }
86 }
87
88 /// Returns `true` if an element is currently attached to this handle.
89 ///
90 /// # Returns
91 ///
92 /// - `bool` - `true` when the value has been initialised.
93 pub fn is_set(&self) -> bool {
94 let cell: *const Option<JsValue> = self.inner.get();
95 // SAFETY: only `is_some()` is called — no `&mut`, no mutation.
96 unsafe { (*cell).is_some() }
97 }
98}
99
100// Blanket impl over the unsized `web_sys::Node` is what most users want,
101// but the macro passes a `JsValue` and the user chooses `T` per use site,
102// so we don't constrain `T` here — `get_cloned`'s `JsCast` bound is the
103// single point where the type check happens.
104//
105// `NodeRef<dyn Any>` (or any unsized type) is accepted by the type system
106// because `T: ?Sized`. The internal `JsValue` storage is independent of
107// `T` so there is no soundness concern.
108
109/// Manual `Clone` impl: `T` is `?Sized` so the `derive` macro (which
110/// requires `T: Clone`) cannot be used. `Rc` clone is cheap and shares
111/// the underlying cell with all clones.
112impl<T: ?Sized> Clone for NodeRef<T> {
113 /// Clones the [`NodeRef`] by reusing shared, cheap-to-clone state where possible.
114 fn clone(&self) -> Self {
115 Self {
116 inner: self.inner.clone(),
117 _marker: PhantomData,
118 }
119 }
120}
121
122impl<T: ?Sized> Default for NodeRef<T> {
123 /// Returns an empty `NodeRef` (no element associated).
124 ///
125 /// The returned handle is independent of any hook context: it is not
126 /// registered as a hook and will never be populated by the renderer
127 /// unless it is the same instance that was returned by
128 /// [`App::use_node_ref`] and then later attached via a `ref:` attribute.
129 /// Prefer [`App::use_node_ref`] inside a component for normal usage.
130 fn default() -> Self {
131 Self {
132 inner: Rc::new(UnsafeCell::new(None)),
133 _marker: PhantomData,
134 }
135 }
136}
137
138impl<T: ?Sized> Debug for NodeRef<T> {
139 /// Formats the [`NodeRef`] via the supplied formatter.
140 ///
141 /// # Arguments
142 ///
143 /// - `&mut Formatter<'_>` - The formatter receiving the formatted output.
144 ///
145 /// # Returns
146 ///
147 /// - `fmt::Result` - Result of the formatting operation.
148 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
149 formatter
150 .debug_struct("NodeRef")
151 .field("is_set", &self.is_set())
152 .finish()
153 }
154}