1//! Context values.
23use std::any::{type_name, Any};
45use slotmap::Key;
67use crate::{create_child_scope, NodeId, Root};
89/// Provide a context value in this scope.
10///
11/// # Panics
12/// This panics if a context value of the same type exists already in this scope. Note that it is
13/// allowed to have context values with the same type in _different_ scopes.
14#[cfg_attr(debug_assertions, track_caller)]
15pub fn provide_context<T: 'static>(value: T) {
16let root = Root::global();
17 provide_context_in_node(root.current_node.get(), value);
18}
1920/// Provide a context value in a new scope.
21///
22/// Since this creates a new scope, this function should never panic. If the context value already
23/// exists in the outer scope, it will be shadowed by the new value in the inner scope _only_.
24/// Outside of the new scope, the old context value will still be accessible.
25///
26/// # Example
27/// ```
28/// # use sycamore_reactive::*;
29/// # let _ = create_root(|| {
30/// provide_context(123);
31/// assert_eq!(use_context::<i32>(), 123);
32///
33/// provide_context_in_new_scope(456, || {
34/// assert_eq!(use_context::<i32>(), 456);
35/// });
36///
37/// assert_eq!(use_context::<i32>(), 123);
38/// # });
39/// ```
40pub fn provide_context_in_new_scope<T: 'static, U>(value: T, f: impl FnOnce() -> U) -> U {
41let mut ret = None;
42 create_child_scope(|| {
43 provide_context(value);
44 ret = Some(f());
45 });
46 ret.unwrap()
47}
4849/// Internal implementation for [`provide_context`].
50#[cfg_attr(debug_assertions, track_caller)]
51fn provide_context_in_node<T: 'static>(id: NodeId, value: T) {
52let root = Root::global();
53let mut nodes = root.nodes.borrow_mut();
54let any: Box<dyn Any> = Box::new(value);
5556let node = &mut nodes[id];
57if node
58 .context
59 .iter()
60 .any(|x| (**x).type_id() == (*any).type_id())
61 {
62panic!(
63"a context with type `{}` exists already in this scope",
64 type_name::<T>()
65 );
66 }
67 node.context.push(any);
68}
6970/// Tries to get a context value of the given type. If no context is found, returns `None`.
71pub fn try_use_context<T: Clone + 'static>() -> Option<T> {
72let root = Root::global();
73let nodes = root.nodes.borrow();
74// Walk up the scope stack until we find one with the context of the right type.
75let mut current = Some(&nodes[root.current_node.get()]);
76while let Some(next) = current {
77for value in &next.context {
78if let Some(value) = value.downcast_ref::<T>().cloned() {
79return Some(value);
80 }
81 }
82// No context of the right type found for this scope. Now check the parent scope.
83if next.parent.is_null() {
84 current = None;
85 } else {
86 current = Some(&nodes[next.parent]);
87 }
88 }
89None
90}
9192/// Get a context with the given type. If no context is found, this panics.
93#[cfg_attr(debug_assertions, track_caller)]
94pub fn use_context<T: Clone + 'static>() -> T {
95if let Some(value) = try_use_context() {
96 value
97 } else {
98panic!("no context of type `{}` found", type_name::<T>())
99 }
100}
101102/// Try to get a context with the given type. If no context is found, returns the value of the
103/// function and sets the value of the context in the current scope.
104pub fn use_context_or_else<T: Clone + 'static, F: FnOnce() -> T>(f: F) -> T {
105 try_use_context().unwrap_or_else(|| {
106let value = f();
107 provide_context(value.clone());
108 value
109 })
110}
111112/// Gets how deep the current scope is from the root/global scope. The value for the global scope
113/// itself is always `0`.
114pub fn use_scope_depth() -> u32 {
115let root = Root::global();
116let nodes = root.nodes.borrow();
117let mut current = Some(&nodes[root.current_node.get()]);
118let mut depth = 0;
119120while let Some(next) = current {
121 depth += 1;
122if next.parent.is_null() {
123 current = None;
124 } else {
125 current = Some(&nodes[next.parent]);
126 }
127 }
128 depth
129}