euv_ui/component/router/view/impl.rs
1use crate::*;
2
3use std::rc::Rc;
4
5/// Implementation of route configuration construction.
6impl EuvRouteConfig {
7 /// Creates a new route configuration.
8 ///
9 /// # Arguments
10 ///
11 /// - `&'static str`: The route path.
12 /// - `F`: The component function.
13 ///
14 /// # Returns
15 ///
16 /// - `EuvRouteConfig`: The route configuration.
17 pub fn new<F>(path: &'static str, component: F) -> Self
18 where
19 F: Fn() -> VirtualNode + 'static,
20 {
21 Self {
22 path,
23 component: Rc::new(component),
24 }
25 }
26}
27
28/// Implementation of router navigation and viewport utilities.
29impl Router {
30 /// Reads the current hash-based route from the browser URL.
31 ///
32 /// # Returns
33 ///
34 /// - `String`: The hash fragment without the leading `#`, or `"/"` if empty.
35 pub fn current_route() -> String {
36 let window: Window = window().expect("no global window exists");
37 let hash: String = window.location().hash().unwrap_or_default();
38 let route: String = hash.strip_prefix('#').unwrap_or(&hash).to_string();
39 if route.is_empty() {
40 "/".to_string()
41 } else {
42 route
43 }
44 }
45
46 /// Navigates to a new hash-based route.
47 ///
48 /// Always defers the actual `location.set_hash()` call to `queueMicrotask`
49 /// to prevent synchronous `hashchange` dispatch while any caller frame is still
50 /// on the stack. This avoids wasm_bindgen's `"closure invoked recursively
51 /// or after being dropped"` error which occurs when `set_hash()` fires
52 /// `hashchange` synchronously and the handler (or the reactive update chain
53 /// it triggers) calls `navigate()` again before the original dispatch finishes.
54 ///
55 /// Multiple rapid `navigate()` calls before the microtask fires are coalesced:
56 /// only the **last** target route wins, as earlier routes were superseded by
57 /// a more recent navigation intent.
58 ///
59 /// # Arguments
60 ///
61 /// - `&str`: The target route path.
62 pub fn navigate(route: &str) {
63 DEFERRED_NAVIGATION.with(|cell: &Cell<Option<String>>| cell.set(Some(route.to_string())));
64 let deferred_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
65 let target_route: Option<String> =
66 DEFERRED_NAVIGATION.with(|cell: &Cell<Option<String>>| cell.take());
67 if let Some(route_value) = target_route {
68 let nav_window: Window = web_sys::window().expect("no global window exists");
69 let nav_location: Location = nav_window.location();
70 let nav_new_hash: String = format!("#{}", route_value);
71 let _ = nav_location.set_hash(&nav_new_hash);
72 }
73 }));
74 let window: Window = window().expect("no global window exists");
75 window.queue_microtask(deferred_closure.as_ref().unchecked_ref::<Function>());
76 deferred_closure.forget();
77 }
78
79 /// Creates a link click handler that navigates to the given route.
80 ///
81 /// Calls `event.prevent_default()` to prevent the `<a>` element's
82 /// default hash navigation, then programmatically navigates via
83 /// `navigate()`. Without `preventDefault`, both the `<a href>` default
84 /// behavior and `navigate()` would fire, potentially creating duplicate
85 /// history entries and causing incorrect browser back/forward behavior.
86 ///
87 /// # Arguments
88 ///
89 /// - `String`: The target route path.
90 ///
91 /// # Returns
92 ///
93 /// - `NativeEventHandler`: An event handler for click events.
94 pub fn link_handler(route: String) -> NativeEventHandler {
95 NativeEventHandler::create("click", move |event: Event| {
96 event.prevent_default();
97 Self::navigate(&route);
98 })
99 }
100
101 /// Checks whether the current viewport width qualifies as a mobile device.
102 ///
103 /// Uses `MOBILE_BREAKPOINT` (768px) as the threshold.
104 ///
105 /// # Returns
106 ///
107 /// - `bool`: `true` if the viewport width is less than the mobile breakpoint.
108 pub fn is_mobile() -> bool {
109 let window: Window = window().expect("no global window exists");
110 let width: f64 = window
111 .inner_width()
112 .ok()
113 .map(|value: JsValue| Number::from(value).value_of())
114 .unwrap_or(0.0);
115 width < MOBILE_BREAKPOINT as f64
116 }
117}