Skip to main content

blitz_dom/node/
select.rs

1//! Live state for a `<select>` element.
2
3/// The selectedness of a `<select>` element's list of options, plus whether its
4/// drop-down is showing.
5///
6/// Positions, not node ids. `selectedIndex` is the spec's own handle on an
7/// option, and a position cannot dangle the way a stored id does once the
8/// option is removed from the tree. The list of options itself is recomputed
9/// from the subtree on every read, so a stale position is at worst out of
10/// range rather than pointing at some unrelated node.
11///
12/// A `Vec<bool>` rather than a single index because `<select multiple>` can
13/// have any number of options selected at once, and a single index would make
14/// the multiple case unrepresentable rather than merely unsupported.
15#[derive(Clone, Debug, Default, PartialEq, Eq)]
16pub struct SelectData {
17    selected: Vec<bool>,
18    open: bool,
19}
20
21impl SelectData {
22    /// Seed the state with one entry per option, in the select's list order.
23    pub fn new(selected: Vec<bool>) -> Self {
24        Self {
25            selected,
26            open: false,
27        }
28    }
29
30    /// Grow or shrink to `len` options, keeping the selectedness of the options
31    /// that are still there.
32    ///
33    /// Layout construction runs again on every resolve, so this is the only
34    /// place a script-added `<option>` gets an entry of its own. Re-seeding
35    /// from the `selected` content attributes instead would throw away whatever
36    /// the user had actually chosen the moment anything else on the page
37    /// changed.
38    pub fn resize(&mut self, len: usize) {
39        self.selected.resize(len, false);
40    }
41
42    /// The number of options the state has entries for.
43    pub fn len(&self) -> usize {
44        self.selected.len()
45    }
46
47    pub fn is_empty(&self) -> bool {
48        self.selected.is_empty()
49    }
50
51    /// Whether the option at `index` is selected. Out of range reads `false`.
52    pub fn is_selected(&self, index: usize) -> bool {
53        self.selected.get(index).copied().unwrap_or(false)
54    }
55
56    /// The first selected option, which is `selectedIndex` for a single select.
57    pub fn selected_index(&self) -> Option<usize> {
58        self.selected.iter().position(|selected| *selected)
59    }
60
61    /// Set one option's selectedness without touching the others. Returns
62    /// whether it changed.
63    pub fn set_selected(&mut self, index: usize, selected: bool) -> bool {
64        match self.selected.get_mut(index) {
65            Some(entry) if *entry != selected => {
66                *entry = selected;
67                true
68            }
69            _ => false,
70        }
71    }
72
73    /// Select `index` and clear every other option. Returns whether anything
74    /// changed.
75    pub fn select_only(&mut self, index: usize) -> bool {
76        if index >= self.selected.len() {
77            return false;
78        }
79        let mut changed = false;
80        for (i, entry) in self.selected.iter_mut().enumerate() {
81            let want = i == index;
82            changed |= *entry != want;
83            *entry = want;
84        }
85        changed
86    }
87
88    /// Deselect every option. Returns whether anything changed.
89    pub fn clear_selection(&mut self) -> bool {
90        let mut changed = false;
91        for entry in self.selected.iter_mut() {
92            changed |= *entry;
93            *entry = false;
94        }
95        changed
96    }
97
98    /// Whether the drop-down list is showing.
99    pub fn is_open(&self) -> bool {
100        self.open
101    }
102
103    pub fn set_open(&mut self, open: bool) {
104        self.open = open;
105    }
106}