1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
use crate::{html::ElementDescriptor, HtmlElement};
use leptos_reactive::{create_render_effect, signal_prelude::*};
use std::cell::Cell;

/// Contains a shared reference to a DOM node created while using the `view`
/// macro to create your UI.
///
/// ```
/// # use leptos::{*, logging::log};
/// use leptos::html::Input;
///
/// #[component]
/// pub fn MyComponent() -> impl IntoView {
///     let input_ref = create_node_ref::<Input>();
///
///     let on_click = move |_| {
///         let node =
///             input_ref.get().expect("input_ref should be loaded by now");
///         // `node` is strongly typed
///         // it is dereferenced to an `HtmlInputElement` automatically
///         log!("value is {:?}", node.value())
///     };
///
///     view! {
///       <div>
///       // `node_ref` loads the input
///       <input _ref=input_ref type="text"/>
///       // the button consumes it
///       <button on:click=on_click>"Click me"</button>
///       </div>
///     }
/// }
/// ```
#[repr(transparent)]
pub struct NodeRef<T: ElementDescriptor + 'static>(
    RwSignal<Option<HtmlElement<T>>>,
);

/// Creates a shared reference to a DOM node created while using the `view`
/// macro to create your UI.
///
/// ```
/// # use leptos::{*, logging::log};
/// use leptos::html::Input;
///
/// #[component]
/// pub fn MyComponent() -> impl IntoView {
///     let input_ref = create_node_ref::<Input>();
///
///     let on_click = move |_| {
///         let node =
///             input_ref.get().expect("input_ref should be loaded by now");
///         // `node` is strongly typed
///         // it is dereferenced to an `HtmlInputElement` automatically
///         log!("value is {:?}", node.value())
///     };
///
///     view! {
///       <div>
///           // `node_ref` loads the input
///           <input _ref=input_ref type="text"/>
///           // the button consumes it
///           <button on:click=on_click>"Click me"</button>
///       </div>
///     }
/// }
/// ```
#[inline(always)]
pub fn create_node_ref<T: ElementDescriptor + 'static>() -> NodeRef<T> {
    NodeRef(create_rw_signal(None))
}

impl<T: ElementDescriptor + 'static> NodeRef<T> {
    /// Creates a shared reference to a DOM node created while using the `view`
    /// macro to create your UI.
    ///
    /// This is identical to [`create_node_ref`].
    ///
    /// ```
    /// # use leptos::{*, logging::log};
    ///
    /// use leptos::html::Input;
    ///
    /// #[component]
    /// pub fn MyComponent() -> impl IntoView {
    ///     let input_ref = NodeRef::<Input>::new();
    ///
    ///     let on_click = move |_| {
    ///         let node =
    ///             input_ref.get().expect("input_ref should be loaded by now");
    ///         // `node` is strongly typed
    ///         // it is dereferenced to an `HtmlInputElement` automatically
    ///         log!("value is {:?}", node.value())
    ///     };
    ///
    ///     view! {
    ///       <div>
    ///           // `node_ref` loads the input
    ///           <input _ref=input_ref type="text"/>
    ///           // the button consumes it
    ///           <button on:click=on_click>"Click me"</button>
    ///       </div>
    ///     }
    /// }
    /// ```
    #[inline(always)]
    #[track_caller]
    pub fn new() -> Self {
        create_node_ref()
    }

    /// Gets the element that is currently stored in the reference.
    ///
    /// This tracks reactively, so that node references can be used in effects.
    /// Initially, the value will be `None`, but once it is loaded the effect
    /// will rerun and its value will be `Some(Element)`.
    #[track_caller]
    #[inline(always)]
    pub fn get(&self) -> Option<HtmlElement<T>>
    where
        T: Clone,
    {
        self.0.get()
    }

    /// Gets the element that is currently stored in the reference.
    ///
    /// This **does not** track reactively.
    #[track_caller]
    #[inline(always)]
    pub fn get_untracked(&self) -> Option<HtmlElement<T>>
    where
        T: Clone,
    {
        self.0.get_untracked()
    }

    #[doc(hidden)]
    /// Loads an element into the reference. This tracks reactively,
    /// so that effects that use the node reference will rerun once it is loaded,
    /// i.e., effects can be forward-declared.
    #[track_caller]
    pub fn load(&self, node: &HtmlElement<T>)
    where
        T: Clone,
    {
        self.0.update(|current| {
            if current.is_some() {
                crate::debug_warn!(
                    "You are setting a NodeRef that has already been filled. \
                     It’s possible this is intentional, but it’s also \
                     possible that you’re accidentally using the same NodeRef \
                     for multiple _ref attributes."
                );
            }
            *current = Some(node.clone());
        });
    }

    /// Runs the provided closure when the `NodeRef` has been connected
    /// with it's [`HtmlElement`].
    #[inline(always)]
    pub fn on_load<F>(self, f: F)
    where
        T: Clone,
        F: FnOnce(HtmlElement<T>) + 'static,
    {
        let f = Cell::new(Some(f));

        create_render_effect(move |_| {
            if let Some(node_ref) = self.get() {
                f.take().unwrap()(node_ref);
            }
        });
    }
}

impl<T: ElementDescriptor> Clone for NodeRef<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T: ElementDescriptor + 'static> Copy for NodeRef<T> {}

impl<T: ElementDescriptor + 'static> Default for NodeRef<T> {
    fn default() -> Self {
        Self::new()
    }
}

cfg_if::cfg_if! {
    if #[cfg(feature = "nightly")] {
        impl<T: Clone + ElementDescriptor + 'static> FnOnce<()> for NodeRef<T> {
            type Output = Option<HtmlElement<T>>;

            #[inline(always)]
            extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
                self.get()
            }
        }

        impl<T: Clone + ElementDescriptor + 'static> FnMut<()> for NodeRef<T> {
            #[inline(always)]
            extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
                self.get()
            }
        }

        impl<T: Clone + ElementDescriptor + Clone + 'static> Fn<()> for NodeRef<T> {
            #[inline(always)]
            extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
                self.get()
            }
        }
    }
}