biji_ui/components/accordion/
context.rs1use 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}