orbital_base_components/collection/state/
selection.rs1use leptos::prelude::*;
2use std::collections::HashSet;
3
4use crate::signals::SignalModel;
5use crate::Handler;
6
7use super::cascade::apply_cascade;
8use super::registry::CollectionRegistry;
9
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11pub enum CollectionSelectionMode {
12 #[default]
13 None,
14 Single,
15 Multi,
16 Checkbox,
17}
18
19#[derive(Clone)]
20pub struct CollectionSelectionState {
21 pub mode: CollectionSelectionMode,
22 pub selected_items: SignalModel<HashSet<String>>,
23 pub default_selected: Vec<String>,
24 pub cascade: bool,
25 pub on_select: Option<Handler<HashSet<String>>>,
26 pub on_selection_toggle: Option<Handler<(String, bool)>>,
27 last_selected: RwSignal<Option<String>>,
28}
29
30impl CollectionSelectionState {
31 pub fn new(
32 mode: CollectionSelectionMode,
33 selected_items: SignalModel<HashSet<String>>,
34 ) -> Self {
35 Self {
36 mode,
37 selected_items,
38 default_selected: Vec::new(),
39 cascade: false,
40 on_select: None,
41 on_selection_toggle: None,
42 last_selected: RwSignal::new(None),
43 }
44 }
45
46 pub fn with_default_selected(mut self, default_selected: Vec<String>) -> Self {
47 if self.selected_items.get().is_empty() && !default_selected.is_empty() {
48 self.selected_items
49 .update(|selected| selected.extend(default_selected.iter().cloned()));
50 }
51 self.default_selected = default_selected;
52 self
53 }
54
55 pub fn with_cascade(mut self, cascade: bool) -> Self {
56 self.cascade = cascade;
57 self
58 }
59
60 pub fn with_on_select(mut self, handler: Handler<HashSet<String>>) -> Self {
61 self.on_select = Some(handler);
62 self
63 }
64
65 pub fn with_on_selection_toggle(mut self, handler: Handler<(String, bool)>) -> Self {
66 self.on_selection_toggle = Some(handler);
67 self
68 }
69
70 pub fn is_selected(&self, item_id: &str) -> bool {
71 self.selected_items
72 .with(|selected| selected.contains(item_id))
73 }
74
75 pub fn notify_select(&self) {
76 if let Some(handler) = &self.on_select {
77 handler.run(self.selected_items.get());
78 }
79 }
80
81 pub fn select_item(
82 &self,
83 item_id: String,
84 keep_existing: bool,
85 should_be_selected: Option<bool>,
86 shift_range: Option<Vec<String>>,
87 registry: Option<&CollectionRegistry>,
88 ) {
89 if self.mode == CollectionSelectionMode::None {
90 return;
91 }
92
93 let currently_selected = self.is_selected(&item_id);
94 let next_selected = should_be_selected.unwrap_or(!currently_selected);
95
96 if let Some(range) = shift_range {
97 self.selected_items.update(|selected| {
98 if !keep_existing {
99 selected.clear();
100 }
101 for id in range {
102 selected.insert(id);
103 }
104 });
105 self.last_selected.set(Some(item_id.clone()));
106 self.notify_select();
107 if self.cascade {
108 if let Some(registry) = registry {
109 apply_cascade(registry, self, &item_id, next_selected);
110 }
111 }
112 return;
113 }
114
115 self.selected_items.update(|selected| match self.mode {
116 CollectionSelectionMode::None => {}
117 CollectionSelectionMode::Single => {
118 selected.clear();
119 if next_selected {
120 selected.insert(item_id.clone());
121 }
122 }
123 CollectionSelectionMode::Multi | CollectionSelectionMode::Checkbox => {
124 if !keep_existing && self.mode == CollectionSelectionMode::Multi {
125 selected.clear();
126 }
127 if next_selected {
128 selected.insert(item_id.clone());
129 } else {
130 selected.remove(&item_id);
131 }
132 }
133 });
134
135 if let Some(handler) = &self.on_selection_toggle {
136 handler.run((item_id.clone(), next_selected));
137 }
138 self.last_selected.set(Some(item_id.clone()));
139 self.notify_select();
140
141 if self.cascade {
142 if let Some(registry) = registry {
143 apply_cascade(registry, self, &item_id, next_selected);
144 }
145 }
146 }
147
148 pub fn last_selected(&self) -> Option<String> {
149 self.last_selected.get_untracked()
150 }
151}