euv-core 0.18.7

A declarative, cross-platform UI framework for Rust with virtual DOM, reactive signals, and HTML macros for WebAssembly.
Documentation
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
use super::*;

/// Implementation of hook context lifecycle and hook index management.
impl HookContext {
    /// Resets the hook index for a new render cycle.
    ///
    /// Sets the internal hook index back to zero so that subsequent
    /// `use_signal` calls start indexing from the beginning of the hook list.
    pub fn reset_index(&mut self) {
        if let Ok(mut inner) = self.get_inner().try_borrow_mut() {
            inner.set_hook_index(0);
        }
    }

    /// Notifies the hook context that a match arm is being entered.
    ///
    /// If the arm index has changed, all existing hooks and cleanups
    /// are cleared and re-initialized for the new arm. If the arm
    /// is unchanged, only the hook index is reset.
    ///
    /// # Arguments
    ///
    /// - `usize` - The index of the new match arm.
    pub fn switch_arm(&mut self, changed: usize) {
        let cleanups: Vec<Box<dyn FnOnce()>>;
        {
            let Ok(mut inner) = self.get_inner().try_borrow_mut() else {
                return;
            };
            if inner.get_arm_changed() == changed {
                drop(inner);
                self.reset_index();
                return;
            }
            cleanups = take(inner.get_mut_cleanups());
            inner.get_mut_hooks().clear();
            inner.set_arm_changed(changed);
        }
        for cleanup in cleanups {
            cleanup();
        }
        // SPA reclamation: every cleanup callback has just run its
        // `Signal::deactivate` (for signals owned by the torn-down hook
        // context), which removes the source from each bridge's
        // dependency set in `BridgeRefsCell`. Any bridge whose DOM
        // element was detached BEFORE the source deactivated is now
        // an orphan (empty dep set, not in `SIGNAL_INNER_REGISTRY`).
        // This is the natural collection moment for those orphan
        // bridge allocations — drive a bounded sweep here so the
        // free happens immediately rather than waiting for the next
        // page transition or a manual sweep from user code.
        //
        // `try_reclaim_inactive` returns the number of allocations
        // reclaimed; we discard the count because the call is
        // opportunistic — failing to reclaim in this frame just
        // defers the work to a later sweep, never blocking the UI.
        let _freed: usize = Signal::<String>::try_reclaim_inactive(usize::MAX);
        self.reset_index();
    }

    /// Creates or reuses a `NodeRef<T>` at the current hook index.
    ///
    /// On the first call at a given hook index, a fresh empty `NodeRef`
    /// is stored. On subsequent re-renders the same instance is returned,
    /// so a ref cloned into a closure stays attached to the live DOM
    /// element across renders.
    ///
    /// The element type `T` is a phantom marker only — we downcast the
    /// stored `Box<dyn Any>` back to `NodeRef<T>` using the same pattern
    /// as `Signal::signal` above. Note that two calls at the same hook
    /// index with different `T` would still match (both are `NodeRef<...>`)
    /// because the `downcast_ref` ignores the phantom parameter.
    ///
    /// # Returns
    ///
    /// - `NodeRef<T>` - A `NodeRef<T>` value.
    pub fn noderef<T>() -> NodeRef<T>
    where
        T: ?Sized + 'static,
    {
        let hook_context: HookContext = Self::current();
        let Ok(mut inner) = hook_context.get_inner().try_borrow_mut() else {
            // Borrow failed (renderer re-entered); fall back to a fresh
            // empty ref so the caller still gets a usable handle.
            return NodeRef::new();
        };
        let index: usize = inner.get_hook_index();
        inner.set_hook_index(index + 1);
        if index < inner.get_hooks().len() {
            // Re-render path: try to reuse the existing NodeRef stored at
            // this hook index. If a different hook type was at this slot
            // (e.g. user swapped `use_signal` for `use_node_ref`), replace
            // it with a fresh ref rather than panicking.
            if let Some(existing) = inner.get_hooks()[index].downcast_ref::<NodeRef<T>>() {
                return existing.clone();
            }
            let new_ref: NodeRef<T> = NodeRef::new();
            inner.get_mut_hooks()[index] = Box::new(new_ref.clone());
            return new_ref;
        }
        let new_ref: NodeRef<T> = NodeRef::new();
        inner.get_mut_hooks().push(Box::new(new_ref.clone()));
        new_ref
    }
}

/// Clones the hook context, sharing the same inner state.
///
/// All clones share the same underlying `Rc<RefCell<HookContextInner>>`,
/// so modifications through one clone are visible through all others.
///
/// # Returns
///
/// - `Self` - A new `HookContext` sharing the same inner state.
impl Clone for HookContext {
    /// Clones the [`HookContext`] by reusing shared, cheap-to-clone state where possible.
    fn clone(&self) -> Self {
        Self::new(self.get_inner().clone())
    }
}

/// Provides a default empty hook context.
///
/// Creates a fresh `Rc<RefCell<HookContextInner>>` with default values
/// (empty hook list, zero hook index, empty cleanup list).
///
/// # Returns
///
/// - `Self` - A new `HookContext` with default inner state.
impl Default for HookContext {
    /// Constructs a default [`HookContext`] value.
    fn default() -> Self {
        Self::new(Rc::new(RefCell::new(HookContextInner::default())))
    }
}

/// Implementation of interval handle lifecycle management.
impl IntervalHandle {
    /// Cancels the associated browser interval timer.
    ///
    /// Calls `window.clearInterval` with the stored interval ID.
    /// After calling this method the interval callback will no longer fire.
    ///
    /// # Panics
    ///
    /// Panics if `window()` is unavailable on the current platform.
    pub fn clear(&self) {
        if let Some(cleanup_window) = web_sys::window() {
            cleanup_window.clear_interval_with_handle(self.get_interval_id());
        }
    }
}

/// Associated functions for hook context management.
///
/// These are crate-internal static methods for managing the active hook
/// context, creating signals, registering cleanups, and scheduling intervals.
impl HookContext {
    /// Returns a shared reference to the current hook context global state.
    ///
    /// SAFETY: Must only be called from the main thread (WASM single-threaded context).
    #[allow(static_mut_refs)]
    fn try_get_current() -> &'static Option<HookContextRc> {
        unsafe { &*CURRENT_HOOK_CONTEXT.get_0().get() }
    }

    /// Returns a mutable reference to the current hook context global state.
    ///
    /// SAFETY: Must only be called from the main thread (WASM single-threaded context).
    #[allow(static_mut_refs)]
    fn try_get_mut_current() -> &'static mut Option<HookContextRc> {
        unsafe { &mut *CURRENT_HOOK_CONTEXT.get_0().get() }
    }

    /// Returns the currently active `HookContext`.
    ///
    /// If no hook context has been set, creates and stores a default one
    /// in the global `CURRENT_HOOK_CONTEXT` cell so subsequent calls
    /// return the same instance.
    ///
    /// # Returns
    ///
    /// - `HookContext` - The currently active hook context.
    pub fn current() -> HookContext {
        match Self::try_get_current() {
            Some(hook_context_rc) => HookContext::new(hook_context_rc.clone()),
            None => {
                let rc: HookContextRc = Rc::new(RefCell::new(HookContextInner::default()));
                *Self::try_get_mut_current() = Some(rc.clone());
                HookContext::new(rc)
            }
        }
    }

    /// Runs a closure with the given `HookContext` set as the active context.
    ///
    /// Saves the previous context, sets the new one, executes the closure,
    /// and restores the previous context afterward.
    ///
    /// # Arguments
    ///
    /// - `HookContext` - The hook context to set as active during closure execution.
    /// - `F: FnOnce() -> R` - The closure to execute with the given context.
    ///
    /// # Returns
    ///
    /// - `R` - The result of the closure execution.
    pub fn with<F, R>(context: HookContext, callback: F) -> R
    where
        F: FnOnce() -> R,
    {
        let previous: Option<HookContextRc> = Self::try_get_mut_current().take();
        *Self::try_get_mut_current() = Some(context.get_inner().clone());
        let result: R = callback();
        *Self::try_get_mut_current() = previous;
        result
    }

    /// Creates a new reactive signal with the given initial value.
    ///
    /// Uses the current `HookContext` to maintain signal identity across
    /// re-renders. On the first call at a given hook index, the signal
    /// is created with `init()` and stored. On subsequent re-renders,
    /// the existing signal at that index is returned unchanged.
    ///
    /// # Arguments
    ///
    /// - `FnOnce() -> T` - A closure that computes the initial value of the signal.
    ///
    /// # Returns
    ///
    /// - `Signal<T>` - A reactive signal containing the initialized or existing value.
    pub fn signal<T, F>(init: F) -> Signal<T>
    where
        T: Clone + PartialEq + 'static,
        F: FnOnce() -> T,
    {
        let hook_context: HookContext = Self::current();
        let Ok(mut inner) = hook_context.get_inner().try_borrow_mut() else {
            return Signal::create(init());
        };
        let index: usize = inner.get_hook_index();
        inner.set_hook_index(index + 1);
        if index < inner.get_hooks().len()
            && let Some(existing) = inner.get_hooks()[index].downcast_ref::<Signal<T>>()
        {
            return *existing;
        }
        let signal: Signal<T> = Signal::create(init());
        inner
            .get_mut_cleanups()
            .push(Box::new(move || signal.deactivate()));
        if index < inner.get_hooks().len() {
            inner.get_mut_hooks()[index] = Box::new(signal);
        } else {
            inner.get_mut_hooks().push(Box::new(signal));
        }
        signal
    }

    /// Registers a cleanup callback that will be executed when the current
    /// hook context is cleared (e.g., when a `match` arm switches).
    ///
    /// This is useful for cleaning up side effects like intervals, timeouts,
    /// or subscriptions that are not automatically managed by signals.
    ///
    /// The cleanup callback is only registered once on the first render.
    /// On subsequent re-renders at the same hook index, this is a no-op.
    ///
    /// # Arguments
    ///
    /// - `FnOnce() + 'static` - The cleanup callback to execute on context teardown.
    pub fn cleanup<F>(cleanup: F)
    where
        F: FnOnce() + 'static,
    {
        let hook_context: HookContext = Self::current();
        let Ok(mut inner) = hook_context.get_inner().try_borrow_mut() else {
            return;
        };
        let index: usize = inner.get_hook_index();
        inner.set_hook_index(index + 1);
        if index < inner.get_hooks().len() {
            return;
        }
        inner.get_mut_cleanups().push(Box::new(cleanup));
        inner.get_mut_hooks().push(Box::new(()));
    }

    /// Registers a `window.addEventListener` callback using event delegation,
    /// automatically removed when the hook context is cleared.
    ///
    /// Uses the global window event proxy registry so that only one
    /// `window.addEventListener` call is made per event name regardless of
    /// how many components listen to the same event. On cleanup, only the
    /// handler entry is removed from the proxy registry; the shared window
    /// listener remains active for other consumers.
    ///
    /// The event listener is only registered once on the first render.
    /// On subsequent re-renders at the same hook index, this is a no-op.
    ///
    /// # Arguments
    ///
    /// - `E: AsRef<str>` - The event name to listen for (e.g., "hashchange", "popstate", "resize").
    /// - `FnMut() + 'static` - The callback to invoke when the event fires.
    pub fn window_event<E, F>(event_name: E, callback: F)
    where
        E: AsRef<str>,
        F: FnMut() + 'static,
    {
        let event_name: &str = event_name.as_ref();
        let hook_context: HookContext = Self::current();
        let Ok(mut inner) = hook_context.get_inner().try_borrow_mut() else {
            return;
        };
        let index: usize = inner.get_hook_index();
        inner.set_hook_index(index + 1);
        if index < inner.get_hooks().len() {
            return;
        }
        let event_name_owned: String = event_name.to_owned();
        let handler_id: usize = Registry::register_window_event(event_name, callback);
        inner.get_mut_cleanups().push(Box::new(move || {
            Registry::unregister_window_event(&event_name_owned, handler_id);
        }));
        inner.get_mut_hooks().push(Box::new(()));
    }

    /// Creates a recurring interval that invokes the given closure at the
    /// specified period, returning an `IntervalHandle` that is automatically
    /// cleared when the hook context is cleared (i.e., when the component
    /// unmounts or a `match` arm switches).
    ///
    /// Unlike calling `set_interval_with_callback_and_timeout_and_arguments_0`
    /// + `Closure::forget()` manually, this hook ensures the interval is
    ///   properly cleaned up, preventing memory leaks and stale callbacks.
    ///
    /// The interval is only created once on the first render.
    /// On subsequent re-renders at the same hook index, the existing handle
    /// is returned unchanged.
    ///
    /// # Arguments
    ///
    /// - `i32` - The interval period in milliseconds.
    /// - `FnMut() + 'static` - The closure to invoke on each interval tick.
    ///
    /// # Returns
    ///
    /// - `IntervalHandle` - A handle that can be used to cancel the interval early.
    ///
    /// # Panics
    ///
    /// Panics if `window()` is unavailable on the current platform.
    pub fn interval<F>(millis: i32, callback: F) -> IntervalHandle
    where
        F: FnMut() + 'static,
    {
        let hook_context: HookContext = Self::current();
        let Ok(mut inner) = hook_context.get_inner().try_borrow_mut() else {
            return IntervalHandle::new(0);
        };
        let index: usize = inner.get_hook_index();
        inner.set_hook_index(index + 1);
        if index < inner.get_hooks().len()
            && let Some(existing) = inner.get_hooks()[index].downcast_ref::<IntervalHandle>()
        {
            return *existing;
        }
        let closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(callback));
        let Some(window) = window() else {
            closure.forget();
            return IntervalHandle::new(0);
        };
        let Ok(interval_id) = window.set_interval_with_callback_and_timeout_and_arguments_0(
            closure.as_ref().unchecked_ref(),
            millis,
        ) else {
            closure.forget();
            return IntervalHandle::new(0);
        };
        closure.forget();
        let handle: IntervalHandle = IntervalHandle::new(interval_id);
        inner.get_mut_cleanups().push(Box::new(move || {
            let Some(cleanup_window) = web_sys::window() else {
                return;
            };
            cleanup_window.clear_interval_with_handle(interval_id);
        }));
        if index < inner.get_hooks().len() {
            inner.get_mut_hooks()[index] = Box::new(handle);
        } else {
            inner.get_mut_hooks().push(Box::new(handle));
        }
        handle
    }
}

/// Inherent implementation of [`HookContext`].
impl HookContext {
    /// Registers a hook value with the current hook context and returns
    /// the existing instance if one was stored at this index from a
    /// previous render cycle.
    ///
    /// This is the public extension point for custom hook types.
    /// `ui` and downstream crates implement `use_form`, `use_i18n`, etc.
    /// on top of this primitive instead of poking at the hook array
    /// directly. `factory` runs once per hook slot on the first render;
    /// subsequent renders in the same arm return the previously stored
    /// instance.
    ///
    /// # Arguments
    ///
    /// - `F: FnOnce() -> T` - Constructor that produces a fresh value
    ///   of type `T` when the slot has never been written.
    ///
    /// # Returns
    ///
    /// - `T: Clone + 'static` - Either the previously-stored
    ///   value (cheap clone / copy) or a fresh one from
    ///   `factory`.
    pub fn use_hook<T, F>(factory: F) -> T
    where
        F: FnOnce() -> T,
        T: Clone + 'static,
    {
        let hook_context: HookContext = Self::current();
        let Ok(mut inner) = hook_context.get_inner().try_borrow_mut() else {
            return factory();
        };
        let index: usize = inner.get_hook_index();
        inner.set_hook_index(index + 1);
        if index < inner.get_hooks().len()
            && let Some(existing) = inner.get_hooks()[index].downcast_ref::<T>()
        {
            return existing.clone();
        }
        let state: T = factory();
        if index < inner.get_hooks().len() {
            inner.get_mut_hooks()[index] = Box::new(state.clone());
        } else {
            inner.get_mut_hooks().push(Box::new(state.clone()));
        }
        state
    }
}