1use crate::node::SelectData;
10use crate::traversal::TreeTraverser;
11use crate::{BaseDocument, local_name};
12use blitz_traits::node_id::NodeId;
13
14impl BaseDocument {
15 pub fn select_options(&self, select_id: NodeId) -> Vec<NodeId> {
25 if !self
26 .get_node(select_id)
27 .is_some_and(|node| node.data.is_element_with_tag_name(&local_name!("select")))
28 {
29 return Vec::new();
30 }
31 TreeTraverser::new_with_root(self, select_id)
32 .filter(|id| {
33 self.get_node(*id)
34 .is_some_and(|node| node.data.is_element_with_tag_name(&local_name!("option")))
35 })
36 .collect()
37 }
38
39 pub fn option_is_disabled(&self, option_id: NodeId) -> bool {
43 let mut current = Some(option_id);
44 while let Some(id) = current {
45 let Some(node) = self.get_node(id) else {
46 return false;
47 };
48 let Some(el) = node.data.downcast_element() else {
49 return false;
50 };
51 if el.attr(local_name!("disabled")).is_some() {
52 return true;
53 }
54 if el.name.local == local_name!("select") {
55 return false;
58 }
59 current = node.parent;
60 }
61 false
62 }
63
64 pub fn option_label(&self, option_id: NodeId) -> String {
68 let Some(node) = self.get_node(option_id) else {
69 return String::new();
70 };
71 if let Some(label) = node.data.downcast_element().and_then(|el| {
72 el.attr(local_name!("label"))
73 .filter(|label| !label.is_empty())
74 }) {
75 return label.to_string();
76 }
77 collapse_whitespace(&node.text_content())
78 }
79
80 pub fn option_value(&self, option_id: NodeId) -> String {
85 let Some(node) = self.get_node(option_id) else {
86 return String::new();
87 };
88 if let Some(value) = node
89 .data
90 .downcast_element()
91 .and_then(|el| el.attr(local_name!("value")))
92 {
93 return value.to_string();
94 }
95 self.option_label(option_id)
96 }
97
98 pub fn initial_select_data(&self, select_id: NodeId) -> SelectData {
107 let options = self.select_options(select_id);
108 let mut selected: Vec<bool> = options
109 .iter()
110 .map(|id| {
111 self.get_node(*id)
112 .and_then(|node| node.data.downcast_element())
113 .is_some_and(|el| el.attr(local_name!("selected")).is_some())
114 })
115 .collect();
116
117 let multiple = self
118 .get_node(select_id)
119 .and_then(|node| node.data.downcast_element())
120 .is_some_and(|el| el.attr(local_name!("multiple")).is_some());
121
122 if !multiple {
123 let last = selected.iter().rposition(|s| *s);
124 let chosen = last.or_else(|| {
125 options
126 .iter()
127 .position(|id| !self.option_is_disabled(*id))
128 .filter(|_| self.select_display_size(select_id) <= 1)
129 });
130 for (i, entry) in selected.iter_mut().enumerate() {
131 *entry = Some(i) == chosen;
132 }
133 }
134
135 SelectData::new(selected)
136 }
137
138 pub fn select_display_size(&self, select_id: NodeId) -> u32 {
145 let Some(el) = self
146 .get_node(select_id)
147 .and_then(|node| node.data.downcast_element())
148 else {
149 return 1;
150 };
151 el.attr(local_name!("size"))
152 .and_then(|size| size.parse::<u32>().ok())
153 .filter(|rows| *rows >= 1)
154 .unwrap_or(if el.attr(local_name!("multiple")).is_some() {
155 4
156 } else {
157 1
158 })
159 }
160
161 pub fn select_selected_option(&self, select_id: NodeId) -> Option<NodeId> {
163 let data = self
164 .get_node(select_id)?
165 .data
166 .downcast_element()?
167 .select_data()?;
168 let index = data.selected_index()?;
169 self.select_options(select_id).get(index).copied()
170 }
171
172 pub fn select_value(&self, select_id: NodeId) -> String {
175 self.select_selected_option(select_id)
176 .map(|option_id| self.option_value(option_id))
177 .unwrap_or_default()
178 }
179
180 pub fn select_label(&self, select_id: NodeId) -> String {
183 self.select_selected_option(select_id)
184 .map(|option_id| self.option_label(option_id))
185 .unwrap_or_default()
186 }
187
188 pub fn select_selected_index(&self, select_id: NodeId) -> Option<usize> {
190 self.get_node(select_id)?
191 .data
192 .downcast_element()?
193 .select_data()?
194 .selected_index()
195 }
196
197 pub fn option_is_selected(&self, option_id: NodeId) -> bool {
201 match self.option_owner_select(option_id) {
202 Some(select_id) => {
203 let index = self
204 .select_options(select_id)
205 .iter()
206 .position(|id| *id == option_id);
207 match (index, self.select_live_data(select_id)) {
208 (Some(index), Some(data)) => data.is_selected(index),
209 _ => self.option_has_selected_attr(option_id),
210 }
211 }
212 None => self.option_has_selected_attr(option_id),
213 }
214 }
215
216 pub fn option_owner_select(&self, option_id: NodeId) -> Option<NodeId> {
219 let mut current = self.get_node(option_id)?.parent;
220 while let Some(id) = current {
221 let node = self.get_node(id)?;
222 if node.data.is_element_with_tag_name(&local_name!("select")) {
223 return Some(id);
224 }
225 current = node.parent;
226 }
227 None
228 }
229
230 fn select_live_data(&self, select_id: NodeId) -> Option<&SelectData> {
231 self.get_node(select_id)?
232 .data
233 .downcast_element()?
234 .select_data()
235 }
236
237 fn option_has_selected_attr(&self, option_id: NodeId) -> bool {
238 self.get_node(option_id)
239 .and_then(|node| node.data.downcast_element())
240 .is_some_and(|el| el.attr(local_name!("selected")).is_some())
241 }
242
243 pub fn set_select_selected_index(&mut self, select_id: NodeId, index: usize) -> bool {
252 let options = self.select_options(select_id);
253 if options
254 .get(index)
255 .is_none_or(|id| self.option_is_disabled(*id))
256 {
257 return false;
258 }
259 let multiple = self
260 .get_node(select_id)
261 .and_then(|node| node.data.downcast_element())
262 .is_some_and(|el| el.attr(local_name!("multiple")).is_some());
263 let option_count = options.len();
264
265 let Some(data) = self
266 .get_node_mut(select_id)
267 .and_then(|node| node.data.downcast_element_mut())
268 .and_then(|el| el.select_data_mut())
269 else {
270 return false;
271 };
272 data.resize(option_count);
276 if multiple {
277 data.set_selected(index, true)
278 } else {
279 data.select_only(index)
280 }
281 }
282
283 pub fn select_index_step(&self, select_id: NodeId, forwards: bool) -> Option<usize> {
286 let options = self.select_options(select_id);
287 if options.is_empty() {
288 return None;
289 }
290 let current = self.select_selected_index(select_id);
291 let mut index = match current {
292 Some(current) => current as isize,
293 None => {
296 return options
297 .iter()
298 .enumerate()
299 .filter(|(_, id)| !self.option_is_disabled(**id))
300 .map(|(i, _)| i)
301 .next_back()
302 .filter(|_| !forwards)
303 .or_else(|| {
304 options
305 .iter()
306 .position(|id| !self.option_is_disabled(*id))
307 .filter(|_| forwards)
308 });
309 }
310 };
311 loop {
312 index += if forwards { 1 } else { -1 };
313 if index < 0 || index as usize >= options.len() {
314 return None;
315 }
316 if !self.option_is_disabled(options[index as usize]) {
317 return Some(index as usize);
318 }
319 }
320 }
321
322 pub fn select_index_edge(&self, select_id: NodeId, last: bool) -> Option<usize> {
324 let options = self.select_options(select_id);
325 let mut enabled = options
326 .iter()
327 .enumerate()
328 .filter(|(_, id)| !self.option_is_disabled(**id))
329 .map(|(i, _)| i);
330 if last {
331 enabled.next_back()
332 } else {
333 enabled.next()
334 }
335 }
336}
337
338fn collapse_whitespace(text: &str) -> String {
342 let mut out = String::with_capacity(text.len());
343 for word in text.split_whitespace() {
344 if !out.is_empty() {
345 out.push(' ');
346 }
347 out.push_str(word);
348 }
349 out
350}