biji_ui/components/accordion/
context.rs

1use std::collections::HashMap;
2
3use leptos::{
4    html::{Button, Div},
5    prelude::*,
6};
7
8use crate::items::{
9    filter_active, next_item, previous_item, FilterActiveItems, Focus, GetIndex, IsActive,
10    ManageFocus, NavigateItems, Toggle,
11};
12
13#[derive(Copy, Clone)]
14pub struct AccordionContext {
15    pub accordion_ref: NodeRef<Div>,
16    pub root: RwSignal<RootContext>,
17}
18
19#[derive(Copy, Clone)]
20pub struct RootContext {
21    pub item_focus: RwSignal<Option<usize>>,
22    pub items: RwSignal<HashMap<usize, ItemContext>>,
23    pub allow_loop: bool,
24}
25
26impl Default for RootContext {
27    fn default() -> Self {
28        Self {
29            item_focus: RwSignal::new(None),
30            items: RwSignal::new(HashMap::new()),
31            allow_loop: false,
32        }
33    }
34}
35
36impl FilterActiveItems<ItemContext> for RootContext {
37    fn filter_active_items(&self) -> Vec<ItemContext> {
38        filter_active(self.items.get())
39    }
40}
41
42impl ManageFocus for RootContext {
43    fn set_focus(&self, index: Option<usize>) {
44        self.item_focus.set(index);
45    }
46
47    fn item_in_focus(&self, index: usize) -> bool {
48        self.item_focus.get() == Some(index)
49    }
50}
51
52impl RootContext {
53    pub fn next_index(&self) -> usize {
54        self.items.get_untracked().len()
55    }
56
57    pub fn upsert_item(&self, index: usize, item: ItemContext) {
58        self.items.update(|items| {
59            *items.entry(index).or_insert(item) = item;
60        });
61    }
62
63    pub fn remove_item(&self, index: usize) {
64        self.items.update(|items| {
65            items.remove(&index);
66        });
67    }
68}
69
70impl NavigateItems<ItemContext> for RootContext {
71    fn navigate_first_item(&self) -> Option<ItemContext> {
72        let active_items = self.filter_active_items();
73
74        if let Some(first) = active_items.get(0) {
75            return Some(first.clone());
76        }
77        None
78    }
79
80    fn navigate_last_item(&self) -> Option<ItemContext> {
81        let active_items = self.filter_active_items();
82
83        if let Some(last) = active_items.last() {
84            return Some(last.clone());
85        }
86        None
87    }
88
89    fn navigate_next_item(&self) -> Option<ItemContext> {
90        let active_items = self.filter_active_items();
91
92        next_item(active_items, self.item_focus.get(), self.allow_loop)
93    }
94
95    fn navigate_previous_item(&self) -> Option<ItemContext> {
96        let active_items = self.filter_active_items();
97
98        previous_item(active_items, self.item_focus.get(), self.allow_loop)
99    }
100}
101
102#[derive(Copy, Clone)]
103pub struct ItemContext {
104    pub index: usize,
105    pub open: RwSignal<bool>,
106    pub disabled: bool,
107    pub trigger_ref: NodeRef<Button>,
108    pub allow_loop: bool,
109}
110
111impl ItemContext {
112    pub fn data_state(&self) -> String {
113        if self.open.get() {
114            String::from("open")
115        } else {
116            String::from("closed")
117        }
118    }
119}
120
121impl Default for ItemContext {
122    fn default() -> Self {
123        Self {
124            index: 0,
125            open: RwSignal::new(false),
126            disabled: false,
127            trigger_ref: NodeRef::default(),
128            allow_loop: false,
129        }
130    }
131}
132
133impl GetIndex<usize> for ItemContext {
134    fn get_index(&self) -> usize {
135        self.index
136    }
137}
138
139impl IsActive for ItemContext {
140    fn is_active(&self) -> bool {
141        !self.disabled
142    }
143}
144
145impl Toggle for ItemContext {
146    fn toggle(&self) {
147        self.open.set(!self.open.get());
148    }
149
150    fn open(&self) {
151        self.open.set(true);
152    }
153
154    fn close(&self) {
155        self.open.set(false);
156    }
157}
158
159impl Focus for ItemContext {
160    fn focus(&self) -> bool {
161        let Some(trigger_ref) = self.trigger_ref.get() else {
162            return false;
163        };
164        let _ = trigger_ref.focus();
165        true
166    }
167}