mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 11:04:40 -05:00
made <Show> accept signals in addition to closures (#4236)
This commit is contained in:
committed by
GitHub
parent
1d0f668dc3
commit
db9f323f8d
@@ -1,14 +1,18 @@
|
|||||||
use crate::{children::ChildrenFn, component, control_flow::Show, IntoView};
|
use crate::{
|
||||||
|
children::ChildrenFn, component, control_flow::Show, show::IntoCondition,
|
||||||
|
IntoView,
|
||||||
|
};
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
use leptos_dom::helpers::TimeoutHandle;
|
use leptos_dom::helpers::TimeoutHandle;
|
||||||
use leptos_macro::view;
|
use leptos_macro::view;
|
||||||
use reactive_graph::{
|
use reactive_graph::{
|
||||||
|
diagnostics::SpecialNonReactiveZone,
|
||||||
effect::RenderEffect,
|
effect::RenderEffect,
|
||||||
owner::{on_cleanup, StoredValue},
|
owner::{on_cleanup, StoredValue},
|
||||||
signal::RwSignal,
|
signal::RwSignal,
|
||||||
traits::{Get, GetUntracked, GetValue, Set, SetValue},
|
traits::{GetValue, Set, SetValue},
|
||||||
wrappers::read::Signal,
|
|
||||||
};
|
};
|
||||||
|
use std::marker::PhantomData;
|
||||||
use tachys::prelude::*;
|
use tachys::prelude::*;
|
||||||
|
|
||||||
/// A component that will show its children when the `when` condition is `true`.
|
/// A component that will show its children when the `when` condition is `true`.
|
||||||
@@ -46,14 +50,16 @@ use tachys::prelude::*;
|
|||||||
/// }
|
/// }
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// Please note, that unlike `Show`, `AnimatedShow` does not support a `fallback` prop.
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||||
#[component]
|
#[component]
|
||||||
pub fn AnimatedShow(
|
pub fn AnimatedShow<M>(
|
||||||
/// The components Show wraps
|
/// The components Show wraps
|
||||||
children: ChildrenFn,
|
children: ChildrenFn,
|
||||||
/// If the component should show or not
|
/// When true the children are shown.
|
||||||
#[prop(into)]
|
/// It accepts a closure that returns a boolean value as well as a boolean signal or plain boolean value.
|
||||||
when: Signal<bool>,
|
when: impl IntoCondition<M>,
|
||||||
/// Optional CSS class to apply if `when == true`
|
/// Optional CSS class to apply if `when == true`
|
||||||
#[prop(optional)]
|
#[prop(optional)]
|
||||||
show_class: &'static str,
|
show_class: &'static str,
|
||||||
@@ -62,17 +68,26 @@ pub fn AnimatedShow(
|
|||||||
hide_class: &'static str,
|
hide_class: &'static str,
|
||||||
/// The timeout after which the component will be unmounted if `when == false`
|
/// The timeout after which the component will be unmounted if `when == false`
|
||||||
hide_delay: Duration,
|
hide_delay: Duration,
|
||||||
|
|
||||||
|
/// Marker for generic parameters. Ignore this.
|
||||||
|
#[prop(optional)]
|
||||||
|
_marker: PhantomData<M>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
|
let when = when.into_condition();
|
||||||
|
|
||||||
|
// Silence warnings about using signals in non-reactive contexts.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let z = SpecialNonReactiveZone::enter();
|
||||||
|
|
||||||
let handle: StoredValue<Option<TimeoutHandle>> = StoredValue::new(None);
|
let handle: StoredValue<Option<TimeoutHandle>> = StoredValue::new(None);
|
||||||
let cls = RwSignal::new(if when.get_untracked() {
|
let cls = RwSignal::new(if when.run() { show_class } else { hide_class });
|
||||||
show_class
|
let show = RwSignal::new(when.run());
|
||||||
} else {
|
|
||||||
hide_class
|
#[cfg(debug_assertions)]
|
||||||
});
|
drop(z);
|
||||||
let show = RwSignal::new(when.get_untracked());
|
|
||||||
|
|
||||||
let eff = RenderEffect::new(move |_| {
|
let eff = RenderEffect::new(move |_| {
|
||||||
if when.get() {
|
if when.run() {
|
||||||
// clear any possibly active timer
|
// clear any possibly active timer
|
||||||
if let Some(h) = handle.get_value() {
|
if let Some(h) = handle.get_value() {
|
||||||
h.clear();
|
h.clear();
|
||||||
@@ -100,8 +115,8 @@ pub fn AnimatedShow(
|
|||||||
});
|
});
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Show when=move || show.get() fallback=|| ()>
|
<Show when=show>
|
||||||
<div class=move || cls.get()>{children()}</div>
|
<div class=cls>{children()}</div>
|
||||||
</Show>
|
</Show>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,71 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
children::{TypedChildrenFn, ViewFn},
|
children::{TypedChildrenFn, ViewFn},
|
||||||
|
prelude::{FunctionMarker, SignalMarker},
|
||||||
IntoView,
|
IntoView,
|
||||||
};
|
};
|
||||||
use leptos_macro::component;
|
use leptos_macro::component;
|
||||||
use reactive_graph::{computed::ArcMemo, traits::Get};
|
use reactive_graph::{computed::ArcMemo, traits::Get};
|
||||||
|
use std::{marker::PhantomData, sync::Arc};
|
||||||
use tachys::either::Either;
|
use tachys::either::Either;
|
||||||
|
|
||||||
|
/// Shows its children whenever the condition `when` prop is `true`.
|
||||||
|
/// Otherwise it renders the `fallback` prop, which defaults to the empty view.
|
||||||
|
///
|
||||||
|
/// The prop `when` can be a closure that returns a bool, a signal of type bool, or a boolean value.
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use leptos::prelude::*;
|
||||||
|
/// #
|
||||||
|
/// # #[component]
|
||||||
|
/// # pub fn Demo() -> impl IntoView {
|
||||||
|
/// let (condition, set_condition) = signal(true);
|
||||||
|
///
|
||||||
|
/// view! {
|
||||||
|
/// <Show when=condition>
|
||||||
|
/// <p>"Hello, world!"</p>
|
||||||
|
/// </Show>
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Or with a closure as the `when` condition:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use leptos::prelude::*;
|
||||||
|
/// #
|
||||||
|
/// # #[component]
|
||||||
|
/// # pub fn Demo() -> impl IntoView {
|
||||||
|
/// let (condition, set_condition) = signal(true);
|
||||||
|
///
|
||||||
|
/// view! {
|
||||||
|
/// <Show when=move || condition.get()>
|
||||||
|
/// <p>"Hello, world!"</p>
|
||||||
|
/// </Show>
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Show<W, C>(
|
pub fn Show<M, C>(
|
||||||
/// The children will be shown whenever the condition in the `when` closure returns `true`.
|
/// The children will be shown whenever the condition in the `when` closure returns `true`.
|
||||||
children: TypedChildrenFn<C>,
|
children: TypedChildrenFn<C>,
|
||||||
/// A closure that returns a bool that determines whether this thing runs
|
/// When true the children are shown, otherwise the fallback.
|
||||||
when: W,
|
/// It accepts a closure that returns a boolean value as well as a boolean signal or plain boolean value.
|
||||||
|
when: impl IntoCondition<M>,
|
||||||
/// A closure that returns what gets rendered if the when statement is false. By default this is the empty view.
|
/// A closure that returns what gets rendered if the when statement is false. By default this is the empty view.
|
||||||
#[prop(optional, into)]
|
#[prop(optional, into)]
|
||||||
fallback: ViewFn,
|
fallback: ViewFn,
|
||||||
|
|
||||||
|
/// Marker for generic parameters. Ignore this.
|
||||||
|
#[prop(optional)]
|
||||||
|
_marker: PhantomData<M>,
|
||||||
) -> impl IntoView
|
) -> impl IntoView
|
||||||
where
|
where
|
||||||
W: Fn() -> bool + Send + Sync + 'static,
|
|
||||||
C: IntoView + 'static,
|
C: IntoView + 'static,
|
||||||
{
|
{
|
||||||
let memoized_when = ArcMemo::new(move |_| when());
|
let when = when.into_condition();
|
||||||
|
let memoized_when = ArcMemo::new(move |_| when.run());
|
||||||
let children = children.into_inner();
|
let children = children.into_inner();
|
||||||
|
|
||||||
move || match memoized_when.get() {
|
move || match memoized_when.get() {
|
||||||
@@ -28,3 +73,44 @@ where
|
|||||||
false => Either::Right(fallback.run()),
|
false => Either::Right(fallback.run()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A closure that returns a bool. Can be converted from a closure, a signal, or a boolean value.
|
||||||
|
pub struct Condition(Arc<dyn Fn() -> bool + Send + Sync + 'static>);
|
||||||
|
|
||||||
|
impl Condition {
|
||||||
|
/// Evaluates the condition and returns its result.
|
||||||
|
pub fn run(&self) -> bool {
|
||||||
|
(self.0)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait to convert various types into a `Condition`.
|
||||||
|
/// Implemented for closures, signals, and boolean values.
|
||||||
|
pub trait IntoCondition<M> {
|
||||||
|
/// Does the conversion
|
||||||
|
fn into_condition(self) -> Condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> IntoCondition<SignalMarker> for S
|
||||||
|
where
|
||||||
|
S: Get<Value = bool> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn into_condition(self) -> Condition {
|
||||||
|
Condition(Arc::new(move || self.get()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> IntoCondition<FunctionMarker> for F
|
||||||
|
where
|
||||||
|
F: Fn() -> bool + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn into_condition(self) -> Condition {
|
||||||
|
Condition(Arc::new(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoCondition<Condition> for Condition {
|
||||||
|
fn into_condition(self) -> Condition {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user