yew_nested_router/
scope.rs1use crate::router::{RouterContext, StackOperation};
2use crate::target::{Mapper, Target};
3use wasm_bindgen::JsValue;
4use yew::prelude::*;
5
6#[derive(Debug)]
7#[doc(hidden)]
8pub struct NavigationTarget<T>
9where
10 T: Target,
11{
12 pub target: T,
13 pub state: JsValue,
14}
15
16impl<T> NavigationTarget<T>
17where
18 T: Target,
19{
20 pub fn map<F, U>(self, f: F) -> NavigationTarget<U>
21 where
22 F: FnOnce(T) -> U,
23 U: Target,
24 {
25 NavigationTarget {
26 target: f(self.target),
27 state: self.state,
28 }
29 }
30}
31
32#[derive(Clone, Debug, PartialEq)]
33pub struct ScopeContext<C>
34where
35 C: Target,
36{
37 pub(crate) upwards: Callback<(NavigationTarget<C>, StackOperation)>,
38 pub(crate) collect: Callback<C, String>,
39}
40
41impl<C> ScopeContext<C>
42where
43 C: Target,
44{
45 pub(crate) fn push(&self, target: C) {
46 self.upwards.emit((
47 (NavigationTarget {
48 target,
49 state: JsValue::null(),
50 }),
51 StackOperation::Push,
52 ));
53 }
54
55 pub(crate) fn replace(&self, target: C) {
56 self.upwards.emit((
57 (NavigationTarget {
58 target,
59 state: JsValue::null(),
60 }),
61 StackOperation::Replace,
62 ));
63 }
64
65 pub(crate) fn push_with(&self, target: C, state: JsValue) {
66 self.upwards
67 .emit((NavigationTarget { target, state }, StackOperation::Push))
68 }
69 pub(crate) fn replace_with(&self, target: C, state: JsValue) {
70 self.upwards
71 .emit((NavigationTarget { target, state }, StackOperation::Replace))
72 }
73
74 pub(crate) fn collect(&self, target: C) -> String {
75 self.collect.emit(target)
76 }
77}
78
79#[derive(Clone, Debug, PartialEq, Properties)]
80pub struct ScopeProps<P, C>
81where
82 P: Target,
83 C: Target,
84{
85 pub mapper: Callback<(), Mapper<P, C>>,
86
87 #[prop_or_default]
88 pub children: Children,
89}
90
91#[function_component(Scope)]
93pub fn scope<P, C>(props: &ScopeProps<P, C>) -> Html
94where
95 P: Target + 'static,
96 C: Target + 'static,
97{
98 let router = use_context::<RouterContext<P>>()
99 .expect("Must be nested under a Router or Nested component of the parent type");
100
101 let parent = use_context::<ScopeContext<P>>()
102 .expect("Must be nested under a Router or Nested component of the parent type");
103
104 let Mapper { downwards, upwards } = props.mapper.emit(());
105
106 let scope = use_memo((parent.clone(), upwards), |(parent, upwards)| {
107 ScopeContext {
108 upwards: {
109 let parent = parent.upwards.clone();
110 let upwards = upwards.clone();
111 Callback::from(
112 move |(target, operation): (NavigationTarget<C>, StackOperation)| {
113 parent.emit((target.map(|target| upwards.emit(target)), operation));
114 },
115 )
116 },
117 collect: {
118 let parent = parent.collect.clone();
119 let upwards = upwards.clone();
120 Callback::from(move |child: C| parent.emit(upwards.emit(child)))
121 },
122 }
123 });
124
125 let base = router.base.clone();
126 let active = router.active();
127
128 let context = use_memo(
129 (
130 base,
131 scope.clone(),
132 active.clone().and_then(|p| downwards.emit(p)),
133 ),
134 |(base, scope, target)| RouterContext {
135 base: base.clone(),
136 scope: scope.clone(),
137 active_target: target.clone(),
138 },
139 );
140
141 html!(
142 <ContextProvider<RouterContext<C>> context={(*context).clone()}>
143 <ContextProvider<ScopeContext<C>> context={(*scope).clone()}>
144 { for props.children.iter() }
145 </ContextProvider<ScopeContext<C>>>
146 </ContextProvider<RouterContext<C>>>
147 )
148}