Skip to main content

resuma/core/
show.rs

1//! Conditional rendering — Leptos-style `Show` without a separate macro system.
2
3use super::effect::Computed;
4use super::signal::{Signal, SignalId};
5use super::view::{Child, Fragment, ShowView, View};
6
7/// Bool reactive source for [`show_signal`] (`Signal<bool>` or `Computed<bool>`).
8pub trait ReactiveBool {
9    fn reactive_id(&self) -> SignalId;
10    fn reactive_bool(&self) -> bool;
11}
12
13impl ReactiveBool for Signal<bool> {
14    fn reactive_id(&self) -> SignalId {
15        self.id()
16    }
17    fn reactive_bool(&self) -> bool {
18        self.peek()
19    }
20}
21
22impl ReactiveBool for Computed<bool> {
23    fn reactive_id(&self) -> SignalId {
24        self.id()
25    }
26    fn reactive_bool(&self) -> bool {
27        self.peek()
28    }
29}
30
31/// Render `children` when `when` is true, otherwise `fallback` or nothing.
32///
33/// Evaluated once at SSR — use [`show_signal`] (via `<Show when={signal}>`) for
34/// client-side toggling.
35pub fn show(when: bool, children: Vec<Child>, fallback: Option<View>) -> View {
36    if when {
37        View::Fragment(Fragment { children })
38    } else if let Some(fb) = fallback {
39        fb
40    } else {
41        View::empty()
42    }
43}
44
45/// Reactive show bound to a bool signal or computed. Both branches are rendered in the DOM;
46/// the client runtime toggles `hidden` on each branch.
47pub fn show_signal<R: ReactiveBool>(
48    when: &R,
49    inverted: bool,
50    children: Vec<Child>,
51    fallback: Option<View>,
52) -> View {
53    let raw = when.reactive_bool();
54    let initial = if inverted { !raw } else { raw };
55    View::Show(ShowView {
56        signal: when.reactive_id(),
57        inverted,
58        initial,
59        children,
60        fallback: fallback.map(Box::new),
61    })
62}
63
64#[cfg(test)]
65mod tests {
66    use super::super::context::{with_context, RenderContext, RenderMode};
67    use super::super::signal;
68    use super::super::view::{Child, View};
69    use super::{show, show_signal};
70    use crate::ssr::render_view;
71
72    #[test]
73    fn show_renders_children_when_true() {
74        let v = show(true, vec![Child::Text("hi".into())], None);
75        assert!(matches!(v, View::Fragment(_)));
76    }
77
78    #[test]
79    fn show_renders_fallback_when_false() {
80        let fb = View::text("no");
81        let v = show(false, vec![], Some(fb));
82        assert!(matches!(v, View::Text(s) if s == "no"));
83    }
84
85    #[test]
86    fn show_signal_emits_resuma_show_marker() {
87        let ctx = RenderContext::new(RenderMode::Ssr);
88        let html = with_context(ctx, || {
89            let on = signal(true);
90            let v = show_signal(
91                &on,
92                false,
93                vec![Child::Text("yes".into())],
94                Some(View::text("no")),
95            );
96            render_view(&v)
97        });
98        assert!(html.contains("<resuma-show"));
99        assert!(html.contains("data-r-show="));
100        assert!(html.contains("data-r-show-if"));
101        assert!(html.contains("yes"));
102        assert!(html.contains("data-r-show-else"));
103        assert!(html.contains("hidden"));
104    }
105}