yew_nested_router/
scope.rs

1use 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/// A component, translating down to the next level.
92#[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}