Skip to main content

orbital_base_components/collection/state/
selection.rs

1use 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}