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}