1use std::sync::Arc;
9
10use tessera_macros::tessera;
11
12pub use tessera_shard::{
13 ShardState, ShardStateLifeCycle,
14 router::{RouterController, RouterDestination},
15};
16
17use crate::{
18 RenderSlot, State,
19 context::{context_from_previous_snapshot_for_instance, provide_context, use_context},
20 runtime::{RuntimePhase, current_component_instance_key_from_scope, current_phase, remember},
21};
22
23#[derive(Clone)]
24struct RouterContext {
25 controller: State<RouterController>,
26}
27
28pub struct RouterDestinationHandle {
31 inner: Arc<dyn RouterDestination>,
32}
33
34impl RouterDestinationHandle {
35 fn clone_destination(&self) -> Arc<dyn RouterDestination> {
36 Arc::clone(&self.inner)
37 }
38}
39
40impl<T> From<T> for RouterDestinationHandle
41where
42 T: RouterDestination + 'static,
43{
44 fn from(value: T) -> Self {
45 Self {
46 inner: Arc::new(value),
47 }
48 }
49}
50
51impl Clone for RouterDestinationHandle {
52 fn clone(&self) -> Self {
53 Self {
54 inner: Arc::clone(&self.inner),
55 }
56 }
57}
58
59impl PartialEq for RouterDestinationHandle {
60 fn eq(&self, other: &Self) -> bool {
61 Arc::ptr_eq(&self.inner, &other.inner)
62 }
63}
64
65impl Eq for RouterDestinationHandle {}
66
67fn resolve_router_controller_state() -> State<RouterController> {
68 match current_phase() {
69 Some(RuntimePhase::Build) => {
70 let context = use_context::<RouterContext>()
71 .expect("Router is missing in build scope. Mount UI inside shard_home.");
72 context.get().controller
73 }
74 Some(RuntimePhase::Input) => {
75 let instance_key = current_component_instance_key_from_scope()
76 .expect("Router command requires an active component scope during input handling");
77 let context = context_from_previous_snapshot_for_instance::<RouterContext>(
78 instance_key,
79 )
80 .expect("Router is missing in input scope. Ensure callbacks run inside shard_home.");
81 context.get().controller
82 }
83 _ => {
84 panic!("Router access must happen during build or input phase");
85 }
86 }
87}
88
89pub(crate) fn current_router_controller() -> State<RouterController> {
90 resolve_router_controller_state()
91}
92
93pub(crate) fn with_current_router_shard_state<T, F, R>(
94 shard_id: &str,
95 life_cycle: ShardStateLifeCycle,
96 f: F,
97) -> R
98where
99 T: Default + Send + Sync + 'static,
100 F: FnOnce(ShardState<T>) -> R,
101{
102 let controller = current_router_controller();
103 controller.with(|router| router.init_or_get_with_lifecycle(shard_id, life_cycle, f))
104}
105
106pub fn router_outlet() {
108 let executed = current_router_controller().with(RouterController::exec_current);
109 assert!(executed, "Router stack should not be empty");
110}
111
112#[tessera(crate)]
146pub fn shard_home(
147 #[prop(into)] root: Option<RouterDestinationHandle>,
148 controller: Option<State<RouterController>>,
149 child: Option<RenderSlot>,
150) {
151 let internal_controller = remember({
152 let root = root.clone();
153 move || match root.clone() {
154 Some(root) => RouterController::with_root_shared(root.clone_destination()),
155 None => RouterController::new(),
156 }
157 });
158 let controller = controller.unwrap_or(internal_controller);
159
160 if root.is_none()
161 && controller == internal_controller
162 && controller.with(RouterController::is_empty)
163 {
164 panic!("shard_home requires `root` when `controller` is not provided");
165 }
166
167 provide_context(
168 || RouterContext { controller },
169 move || {
170 if let Some(child) = child {
171 child.render();
172 } else {
173 router_outlet();
174 }
175 },
176 );
177}