use leptos::*;
use leptos_dom::Transparent;
use std::cell::Cell;
api_planning! {
view! { cx,
<If signal=bool_signal>
<Then>
"thing to show if bool_signal is true"
</Then>
<ElseIf signal=bool_signal_b>
"Other thing to show"
</ElseIf>
<Else>
"The fallback"
</Else>
</If>
}
}
#[component]
pub fn If(
cx: Scope,
#[prop(into)]
signal: MaybeSignal<bool>,
children: Box<dyn Fn(Scope) -> Fragment>,
) -> impl IntoView {
let signal = create_memo(cx, move |_| signal.get());
let children = children(cx);
let if_blocks = children
.as_children()
.iter()
.filter_map(View::as_transparent)
.cloned()
.collect::<Vec<_>>();
#[cfg(debug_assertions)]
run_debug_checks(&if_blocks);
let last_rendered_block = Cell::<Option<usize>>::new(None);
let child = Cell::new(().into_view(cx));
move || {
let mut if_blocks = if_blocks
.iter()
.filter_map(Transparent::downcast_ref::<IfBlock>)
.enumerate();
if_blocks.clone().skip(1).for_each(|(_, block)| {
if let IfBlock::ElseIf { signal, .. } = block {
signal.track();
}
});
if signal.get() {
if last_rendered_block.get() != Some(0) {
last_rendered_block.set(Some(0));
let new_child = if_blocks.next().unwrap().1.render(cx).into_view(cx);
child.set(new_child);
}
} else if let Some((i, block)) =
if_blocks.find(|(_, block)| block.is_true())
{
if last_rendered_block.get() != Some(i) {
last_rendered_block.set(Some(i));
let new_child = block.render(cx).into_view(cx);
child.set(new_child);
}
} else {
last_rendered_block.set(None);
child.set(().into_view(cx));
}
let view = child.take();
child.set(view.clone());
view
}
}
#[component(transparent)]
pub fn Then(
cx: Scope,
children: Box<dyn Fn(Scope) -> Fragment>,
) -> impl IntoView {
let _ = cx;
IfBlock::If { children }
}
#[component(transparent)]
pub fn ElseIf(
cx: Scope,
#[prop(into)]
signal: MaybeSignal<bool>,
children: Box<dyn Fn(Scope) -> Fragment>,
) -> impl IntoView {
let signal = create_memo(cx, move |_| signal.get());
IfBlock::ElseIf { signal, children }
}
#[component(transparent)]
pub fn Else(
cx: Scope,
children: Box<dyn Fn(Scope) -> Fragment>,
) -> impl IntoView {
let _ = cx;
IfBlock::Else { children }
}
pub enum IfBlock {
If {
children: Box<dyn Fn(Scope) -> Fragment>,
},
ElseIf {
signal: Memo<bool>,
children: Box<dyn Fn(Scope) -> Fragment>,
},
Else {
children: Box<dyn Fn(Scope) -> Fragment>,
},
}
impl IfBlock {
fn is_true(&self) -> bool {
if let Self::ElseIf { signal, .. } = self {
signal.get()
} else {
self.is_else()
}
}
fn is_if(&self) -> bool {
matches!(self, Self::If { .. })
}
fn is_else(&self) -> bool {
matches!(self, Self::Else { .. })
}
fn render(&self, cx: Scope) -> Fragment {
match self {
Self::If { children } => children(cx),
Self::ElseIf { children, .. } => children(cx),
Self::Else { children } => children(cx),
}
}
}
impl IntoView for IfBlock {
fn into_view(self, _: Scope) -> View {
View::Transparent(Transparent::new(self))
}
}
#[cfg(debug_assertions)]
fn run_debug_checks(if_blocks: &[Transparent]) {
let if_blocks = if_blocks
.iter()
.filter_map(Transparent::downcast_ref::<IfBlock>);
assert!(
if_blocks.clone().next().unwrap().is_if(),
"`<Show />` must be the first child of `<If />`"
);
assert_eq!(
if_blocks.clone().filter(|block| block.is_if()).count(),
1,
"there must not be more than 1 `<Show />` children within `<If />`"
);
if let Some(pos) = if_blocks.clone().position(|block| block.is_else()) {
assert_eq!(
pos,
if_blocks.clone().count() - 1,
"`<Else />` must be the last child of `<If />`"
);
}
assert!(
if_blocks.filter(|block| block.is_else()).count() <= 1,
"there must not be more than 1 `<Else />` children within `<If />`"
);
}