leptos/show_let.rs
1use crate::{children::ViewFn, IntoView};
2use leptos_macro::component;
3use reactive_graph::traits::Get;
4use std::{marker::PhantomData, sync::Arc};
5use tachys::either::Either;
6
7/// Like `<Show>` but for `Option`. This is a shortcut for
8///
9/// ```ignore
10/// value.map(|value| {
11/// view! { ... }
12/// })
13/// ```
14///
15/// If you specify a `fallback` it is equvalent to
16///
17/// ```ignore
18/// value
19/// .map(
20/// |value| children(value),
21/// )
22/// .unwrap_or_else(fallback)
23/// ```
24///
25/// ## Example
26///
27/// ```
28/// # use leptos::prelude::*;
29/// #
30/// # #[component]
31/// # pub fn Example() -> impl IntoView {
32/// let (opt_value, set_opt_value) = signal(None::<i32>);
33///
34/// view! {
35/// <ShowLet some=opt_value let:value>
36/// "We have a value: " {value}
37/// </ShowLet>
38/// }
39/// # }
40/// ```
41///
42/// You can also specify a fallback:
43/// ```
44/// # use leptos::prelude::*;
45/// #
46/// # #[component]
47/// # pub fn Example() -> impl IntoView {
48/// let (opt_value, set_opt_value) = signal(None::<i32>);
49///
50/// view! {
51/// <ShowLet some=opt_value let:value fallback=|| "Got nothing">
52/// "We have a value: " {value}
53/// </ShowLet>
54/// }
55/// # }
56/// ```
57///
58/// In addition to signals you can also use a closure that returns an `Option`:
59///
60/// ```
61/// # use leptos::prelude::*;
62/// #
63/// # #[component]
64/// # pub fn Example() -> impl IntoView {
65/// let (opt_value, set_opt_value) = signal(None::<i32>);
66///
67/// view! {
68/// <ShowLet some=move || opt_value.get().map(|v| v * 2) let:value>
69/// "We have a value: " {value}
70/// </ShowLet>
71/// }
72/// # }
73/// ```
74#[component]
75pub fn ShowLet<T, ChFn, V, M>(
76 /// The children will be shown whenever `value` is `Some`.
77 ///
78 /// They take the inner value as an argument. Use `let:` to bind the value to a variable.
79 children: ChFn,
80
81 /// A signal of type `Option` or a closure that returns an `Option`.
82 /// If the value is `Some`, the children will be shown.
83 /// Otherwise the fallback will be shown, if present.
84 some: impl IntoOptionGetter<T, M>,
85
86 /// A closure that returns what gets rendered when the value is `None`.
87 /// By default this is the empty view.
88 ///
89 /// You can think of it as the closure inside `.unwrap_or_else(|| fallback())`.
90 #[prop(optional, into)]
91 fallback: ViewFn,
92
93 /// Marker for generic parameters. Ignore this.
94 #[prop(optional)]
95 _marker: PhantomData<(T, M)>,
96) -> impl IntoView
97where
98 ChFn: Fn(T) -> V + Send + Clone + 'static,
99 V: IntoView + 'static,
100 T: 'static,
101{
102 let getter = some.into_option_getter();
103
104 move || {
105 let children = children.clone();
106 let fallback = fallback.clone();
107
108 getter
109 .run()
110 .map(move |t| Either::Left(children(t)))
111 .unwrap_or_else(move || Either::Right(fallback.run()))
112 }
113}
114
115/// Servers as a wrapper for both, an `Option` signal or a closure that returns an `Option`.
116pub struct OptionGetter<T>(Arc<dyn Fn() -> Option<T> + Send + Sync + 'static>);
117
118impl<T> Clone for OptionGetter<T> {
119 fn clone(&self) -> Self {
120 Self(Arc::clone(&self.0))
121 }
122}
123
124impl<T> OptionGetter<T> {
125 /// Runs the getter and returns the result.
126 pub fn run(&self) -> Option<T> {
127 (self.0)()
128 }
129}
130
131/// Conversion trait for creating an `OptionGetter` from a closure or a signal.
132pub trait IntoOptionGetter<T, M> {
133 /// Converts the given value into an `OptionGetter`.
134 fn into_option_getter(self) -> OptionGetter<T>;
135}
136
137/// Marker type for creating an `OptionGetter` from a closure.
138/// Used so that the compiler doesn't complain about double implementations of the trait `IntoOptionGetter`.
139pub struct FunctionMarker;
140
141impl<T, F> IntoOptionGetter<T, FunctionMarker> for F
142where
143 F: Fn() -> Option<T> + Send + Sync + 'static,
144{
145 fn into_option_getter(self) -> OptionGetter<T> {
146 OptionGetter(Arc::new(self))
147 }
148}
149
150/// Marker type for creating an `OptionGetter` from a signal.
151/// Used so that the compiler doesn't complain about double implementations of the trait `IntoOptionGetter`.
152pub struct SignalMarker;
153
154impl<T, S> IntoOptionGetter<T, SignalMarker> for S
155where
156 S: Get<Value = Option<T>> + Clone + Send + Sync + 'static,
157{
158 fn into_option_getter(self) -> OptionGetter<T> {
159 let cloned = self.clone();
160 OptionGetter(Arc::new(move || cloned.get()))
161 }
162}