1use crate::{BaseDocument, local_name, node::SpecialElementData, traversal::TreeTraverser};
3use apace_traits::node_id::NodeId;
4use style_dom::ElementState;
5
6impl BaseDocument {
7 fn check_kind(&self, id: NodeId) -> Option<&str> {
8 let element = self.get_node(id)?.element_data()?;
9 if element.name.local != local_name!("input") {
10 return None;
11 }
12 element
13 .attr(local_name!("type"))
14 .filter(|kind| matches!(*kind, "checkbox" | "radio"))
15 }
16 fn radio_root(&self, mut id: NodeId) -> Option<NodeId> {
17 while let Some(parent) = self.get_node(id)?.parent {
18 id = parent;
19 }
20 Some(id)
21 }
22 fn radio_form(&self, id: NodeId, root: NodeId) -> Option<NodeId> {
23 let element = self.get_node(id)?.element_data()?;
24 if let Some(name) = element.attr(local_name!("form")) {
25 return TreeTraverser::new_with_root(self, root).find(|candidate| {
27 self.get_node(*candidate)
28 .and_then(|n| n.element_data())
29 .is_some_and(|e| {
30 e.name.local == local_name!("form")
31 && e.attr(local_name!("id")) == Some(name)
32 })
33 });
34 }
35 let mut cursor = self.get_node(id)?.parent;
36 while let Some(id) = cursor {
37 let node = self.get_node(id)?;
38 if node.data.is_element_with_tag_name(&local_name!("form")) {
39 return Some(id);
40 }
41 cursor = node.parent;
42 }
43 None
44 }
45 pub fn radio_group(&self, id: NodeId) -> Vec<NodeId> {
49 if self.check_kind(id) != Some("radio") {
50 return Vec::new();
51 }
52 let Some(name) = self
53 .get_node(id)
54 .and_then(|n| n.attr(local_name!("name")))
55 .filter(|s| !s.is_empty())
56 else {
57 return vec![id];
58 };
59 let root = self.radio_root(id).unwrap();
60 let form = self.radio_form(id, root);
61 TreeTraverser::new_with_root(self, root)
62 .filter(|peer| {
63 self.check_kind(*peer) == Some("radio")
64 && self
65 .get_node(*peer)
66 .and_then(|n| n.attr(local_name!("name")))
67 == Some(name)
68 && self.radio_form(*peer, root) == form
69 })
70 .collect()
71 }
72 fn live_checked(&self, id: NodeId) -> bool {
73 let element = self.get_node(id).unwrap().element_data().unwrap();
74 element
75 .checkbox_input_checked()
76 .unwrap_or_else(|| element.element_state.contains(ElementState::CHECKED))
77 }
78 fn write_checked(&mut self, id: NodeId, checked: bool) -> bool {
79 let before = self.live_checked(id);
80 let initialized = self
81 .get_node(id)
82 .unwrap()
83 .element_data()
84 .unwrap()
85 .checkbox_input_checked()
86 .is_some();
87 if before == checked && initialized {
88 return false;
89 }
90 self.snapshot_node_and(id, ElementState::CHECKED, |node| {
91 let element = node.element_data_mut().unwrap();
92 if !matches!(element.special_data, SpecialElementData::CheckboxInput(_)) {
94 element.special_data = SpecialElementData::CheckboxInput(checked);
95 }
96 element.set_checkbox_input_checked(checked);
97 node.mark_ancestors_dirty();
98 node.insert_damage(crate::layout::damage::ALL_DAMAGE);
99 });
100 true
101 }
102 pub fn set_input_checked(&mut self, id: NodeId, checked: bool) -> bool {
105 let Some(kind) = self.check_kind(id) else {
106 return false;
107 };
108 let peers = if kind == "radio" && checked {
109 self.radio_group(id)
110 } else {
111 Vec::new()
112 };
113 let mut changed = self.write_checked(id, checked);
114 for peer in peers {
115 if peer != id && self.live_checked(peer) {
116 changed |= self.write_checked(peer, false);
117 }
118 }
119 if changed {
120 self.shell_provider.request_redraw();
121 }
122 true
123 }
124 pub fn activate_radio(&mut self, id: NodeId) -> Option<bool> {
127 if self.check_kind(id) != Some("radio") {
128 return None;
129 }
130 let changed = !self.live_checked(id);
131 self.set_input_checked(id, true);
132 Some(changed)
133 }
134 pub(crate) fn reconcile_radio(&mut self, id: NodeId) {
135 if self.check_kind(id) == Some("radio") && self.live_checked(id) {
136 self.set_input_checked(id, true);
137 }
138 }
139 pub(crate) fn reconcile_radio_subtrees(&mut self, roots: &[NodeId]) {
140 let radios: Vec<_> = roots
143 .iter()
144 .flat_map(|root| TreeTraverser::new_with_root(self, *root))
145 .filter(|id| self.check_kind(*id) == Some("radio") && self.live_checked(*id))
146 .collect();
147 for id in radios {
148 self.set_input_checked(id, true);
149 }
150 }
151}