use leptos::prelude::{Get, Memo, RwSignal, Signal, Track};
use leptos::reactive::signal::ReadSignal;
#[derive(Clone, Debug)]
pub struct ClassCondition(ClassConditionKind);
#[derive(Clone, Debug)]
enum ClassConditionKind {
Always,
Never,
When(Signal<bool>),
Not(Signal<bool>),
}
impl ClassCondition {
pub(crate) fn always() -> Self {
Self(ClassConditionKind::Always)
}
pub(crate) fn never() -> Self {
Self(ClassConditionKind::Never)
}
pub(crate) fn when_signal(signal: impl Into<Signal<bool>>) -> Self {
Self(ClassConditionKind::When(signal.into()))
}
pub(crate) fn when_predicate(predicate: impl Fn() -> bool + Send + Sync + 'static) -> Self {
Self(ClassConditionKind::When(Signal::derive(predicate)))
}
pub(crate) fn is_active(&self) -> bool {
match &self.0 {
ClassConditionKind::Always => true,
ClassConditionKind::Never => false,
ClassConditionKind::When(when) => when.get(),
ClassConditionKind::Not(when) => !when.get(),
}
}
pub(crate) fn is_reactive(&self) -> bool {
matches!(
&self.0,
ClassConditionKind::When(_) | ClassConditionKind::Not(_)
)
}
pub(crate) fn touch_reactive_dependency(&self) {
match &self.0 {
ClassConditionKind::When(when) | ClassConditionKind::Not(when) => {
when.track();
}
ClassConditionKind::Always | ClassConditionKind::Never => {}
}
}
pub(crate) fn negate(self) -> Self {
Self(match self.0 {
ClassConditionKind::Always => ClassConditionKind::Never,
ClassConditionKind::Never => ClassConditionKind::Always,
ClassConditionKind::When(when) => ClassConditionKind::Not(when),
ClassConditionKind::Not(when) => ClassConditionKind::When(when),
})
}
pub(crate) fn or(self, other: Self) -> Self {
use ClassConditionKind::{Always, Never, Not, When};
Self(match (self.0, other.0) {
(Always, _) | (_, Always) => Always,
(Never, k) | (k, Never) => k,
(When(a), When(b)) => When(Signal::derive(move || a.get() || b.get())),
(When(a), Not(b)) => When(Signal::derive(move || a.get() || !b.get())),
(Not(a), When(b)) => When(Signal::derive(move || !a.get() || b.get())),
(Not(a), Not(b)) => When(Signal::derive(move || !a.get() || !b.get())),
})
}
}
impl From<bool> for ClassCondition {
fn from(active: bool) -> Self {
if active {
Self::always()
} else {
Self::never()
}
}
}
impl From<Signal<bool>> for ClassCondition {
fn from(signal: Signal<bool>) -> Self {
Self::when_signal(signal)
}
}
impl From<ReadSignal<bool>> for ClassCondition {
fn from(signal: ReadSignal<bool>) -> Self {
Self::when_signal(signal)
}
}
impl From<RwSignal<bool>> for ClassCondition {
fn from(signal: RwSignal<bool>) -> Self {
Self::when_signal(signal)
}
}
impl From<Memo<bool>> for ClassCondition {
fn from(memo: Memo<bool>) -> Self {
Self::when_signal(memo)
}
}
impl<F> From<F> for ClassCondition
where
F: Fn() -> bool + Send + Sync + 'static,
{
fn from(predicate: F) -> Self {
Self::when_predicate(predicate)
}
}
#[cfg(test)]
mod negate {
use assertr::prelude::*;
use leptos::prelude::{Set, signal};
use crate::condition::ClassCondition;
#[test]
fn always_negates_to_never() {
let negated = ClassCondition::always().negate();
assert_that!(negated.is_active()).is_false();
assert_that!(negated.is_reactive()).is_false();
}
#[test]
fn never_negates_to_always() {
let negated = ClassCondition::never().negate();
assert_that!(negated.is_active()).is_true();
assert_that!(negated.is_reactive()).is_false();
}
#[test]
fn when_negates_to_inverse_and_stays_reactive() {
let (s, set_s) = signal(false);
let negated = ClassCondition::when_signal(s).negate();
assert_that!(negated.is_reactive()).is_true();
assert_that!(negated.is_active()).is_true();
set_s.set(true);
assert_that!(negated.is_active()).is_false();
}
#[test]
fn double_negation_restores_original_behavior() {
let (s, set_s) = signal(false);
let restored = ClassCondition::when_signal(s).negate().negate();
assert_that!(restored.is_reactive()).is_true();
assert_that!(restored.is_active()).is_false();
set_s.set(true);
assert_that!(restored.is_active()).is_true();
}
}
#[cfg(test)]
mod or {
use assertr::prelude::*;
use leptos::prelude::{Set, signal};
use crate::condition::ClassCondition;
mod static_absorption {
use super::*;
#[test]
fn always_or_when_is_always_and_not_reactive() {
let (s, _) = signal(false);
let combined = ClassCondition::always().or(ClassCondition::when_signal(s));
assert_that!(combined.is_active()).is_true();
assert_that!(combined.is_reactive()).is_false();
}
#[test]
fn when_or_always_is_always() {
let (s, _) = signal(false);
let combined = ClassCondition::when_signal(s).or(ClassCondition::always());
assert_that!(combined.is_active()).is_true();
assert_that!(combined.is_reactive()).is_false();
}
#[test]
fn never_or_never_is_never() {
let combined = ClassCondition::never().or(ClassCondition::never());
assert_that!(combined.is_active()).is_false();
assert_that!(combined.is_reactive()).is_false();
}
#[test]
fn never_or_when_yields_when() {
let (s, set_s) = signal(false);
let combined = ClassCondition::never().or(ClassCondition::when_signal(s));
assert_that!(combined.is_reactive()).is_true();
assert_that!(combined.is_active()).is_false();
set_s.set(true);
assert_that!(combined.is_active()).is_true();
}
}
mod reactive_combinations {
use super::*;
#[test]
fn when_or_when_tracks_both_signals() {
let (a, set_a) = signal(false);
let (b, set_b) = signal(false);
let combined = ClassCondition::when_signal(a).or(ClassCondition::when_signal(b));
assert_that!(combined.is_reactive()).is_true();
assert_that!(combined.is_active()).is_false();
set_a.set(true);
assert_that!(combined.is_active()).is_true();
set_a.set(false);
set_b.set(true);
assert_that!(combined.is_active()).is_true();
}
#[test]
fn when_or_not_inverts_the_second_signal() {
let (a, set_a) = signal(false);
let (b, set_b) = signal(true);
let combined =
ClassCondition::when_signal(a).or(ClassCondition::when_signal(b).negate());
assert_that!(combined.is_active()).is_false();
set_b.set(false);
assert_that!(combined.is_active()).is_true();
set_b.set(true);
set_a.set(true);
assert_that!(combined.is_active()).is_true();
}
#[test]
fn not_or_when_inverts_the_first_signal() {
let (a, set_a) = signal(true);
let (b, set_b) = signal(false);
let combined = ClassCondition::when_signal(a)
.negate()
.or(ClassCondition::when_signal(b));
assert_that!(combined.is_active()).is_false();
set_a.set(false);
assert_that!(combined.is_active()).is_true();
set_a.set(true);
set_b.set(true);
assert_that!(combined.is_active()).is_true();
}
#[test]
fn not_or_not_is_or_of_negations() {
let (a, set_a) = signal(true);
let (b, set_b) = signal(true);
let combined = ClassCondition::when_signal(a)
.negate()
.or(ClassCondition::when_signal(b).negate());
assert_that!(combined.is_active()).is_false();
set_a.set(false);
assert_that!(combined.is_active()).is_true();
set_a.set(true);
set_b.set(false);
assert_that!(combined.is_active()).is_true();
}
}
}