use std::cell::RefCell;
use std::collections::HashMap;
use std::mem::ManuallyDrop;
use reactive_core::{RwSignal, signal};
thread_local! {
static NAMED: ManuallyDrop<RefCell<HashMap<String, RwSignal<bool>>>> =
ManuallyDrop::new(RefCell::new(HashMap::new()));
}
pub fn state(id: &str) -> RwSignal<bool> {
if let Some(existing) = NAMED.with(|named| named.borrow().get(id).cloned()) {
return existing;
}
let created = signal(false);
NAMED.with(|named| {
named
.borrow_mut()
.entry(id.to_string())
.or_insert(created)
.clone()
})
}
pub fn open(id: &str) {
state(id).set(true);
}
pub fn close(id: &str) {
state(id).set(false);
}
pub fn toggle(id: &str) {
let s = state(id);
let open = s.peek();
s.set(!open);
}
pub fn is_open(id: &str) -> bool {
state(id).get()
}
pub fn peek_open(id: &str) -> bool {
state(id).peek()
}
#[cfg(test)]
mod tests {
use super::*;
fn reset() {
NAMED.with(|named| named.borrow_mut().clear());
}
#[test]
fn a_name_resolves_to_one_shared_signal() {
reset();
let first = state("confirm");
let second = state("confirm");
first.set(true);
assert!(second.get(), "both handles are the same signal");
assert!(!state("other").get(), "a different name is independent");
}
#[test]
fn opening_a_name_before_its_overlay_is_built_still_opens_it() {
reset();
open("settings");
assert!(state("settings").get());
}
#[test]
fn open_close_and_toggle_drive_the_same_state() {
reset();
assert!(!peek_open("panel"));
open("panel");
assert!(peek_open("panel"));
close("panel");
assert!(!peek_open("panel"));
toggle("panel");
assert!(peek_open("panel"));
toggle("panel");
assert!(!peek_open("panel"));
}
#[test]
fn is_open_is_reactive() {
reset();
let seen = std::rc::Rc::new(RefCell::new(Vec::new()));
let s = seen.clone();
let _e = reactive_core::effect(move || s.borrow_mut().push(is_open("banner")));
open("banner");
close("banner");
assert_eq!(*seen.borrow(), vec![false, true, false]);
}
}