Skip to content

Commit dd02df2

Browse files
Finish role helpers
1 parent 5e1a10b commit dd02df2

1 file changed

Lines changed: 116 additions & 19 deletions

File tree

packages/dom/src/role_helpers.rs

Lines changed: 116 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
use std::sync::LazyLock;
1+
use std::{collections::HashMap, sync::LazyLock};
22

33
use aria_query::{
44
AriaRoleDefinitionKey, AriaRoleRelationConcept, AriaRoleRelationConceptAttributeConstraint,
55
ELEMENT_ROLES,
66
};
7+
use dom_accessibility_api::{
8+
compute_accessible_description, compute_accessible_name, ComputeTextAlternativeOptions,
9+
};
710
use wasm_bindgen::JsCast;
8-
use web_sys::{Element, HtmlElement, HtmlInputElement, HtmlOptionElement, Node};
11+
use web_sys::{Element, HtmlElement, HtmlInputElement, HtmlOptionElement};
912

10-
use crate::types::ByRoleOptionsCurrent;
13+
use crate::{pretty_dom, types::ByRoleOptionsCurrent, util::html_collection_to_vec};
1114

1215
struct ElementRole {
1316
r#match: Box<dyn Fn(&Element) -> bool + Send + Sync>,
@@ -179,26 +182,120 @@ pub fn get_implicit_aria_roles(current_node: &Element) -> Vec<AriaRoleDefinition
179182
vec![]
180183
}
181184

182-
pub fn get_roles(_container: Node) -> Vec<String> {
183-
fn _flatten_dom(node: Node) -> Vec<Node> {
184-
let nodes = vec![node.clone()];
185-
if let Some(_element) = node.dyn_ref::<Element>() {
186-
todo!()
187-
// nodes.extend(element.children());
188-
}
189-
nodes
185+
#[derive(Clone, Default)]
186+
pub struct GetRolesOptions {
187+
pub hidden: Option<bool>,
188+
}
189+
190+
pub fn get_roles(
191+
container: Element,
192+
options: GetRolesOptions,
193+
) -> HashMap<AriaRoleDefinitionKey, Vec<Element>> {
194+
fn flatten_dom(element: Element) -> Vec<Element> {
195+
let mut elements = vec![element.clone()];
196+
elements.extend(
197+
html_collection_to_vec::<Element>(element.children())
198+
.into_iter()
199+
.flat_map(flatten_dom)
200+
.collect::<Vec<_>>(),
201+
);
202+
elements
190203
}
191204

192-
todo!()
193-
// flatten_dom(contaier).into_iter().filter(|_element| {
194-
// // TODO
195-
// false
196-
// })
197-
//.fold(init, f)
205+
let hidden = options.hidden.unwrap_or(false);
206+
207+
flatten_dom(container)
208+
.into_iter()
209+
.filter(|element| hidden || !is_inaccessible(element))
210+
.fold(HashMap::new(), |mut acc, element| {
211+
// TODO: This violates html-aria which does not allow any role on every element.
212+
let roles = if element.has_attribute("role") {
213+
element
214+
.get_attribute("role")
215+
.expect("Attribute should exist.")
216+
.split(' ')
217+
.filter_map(|role| role.parse::<AriaRoleDefinitionKey>().ok())
218+
.take(1)
219+
.collect::<Vec<_>>()
220+
} else {
221+
get_implicit_aria_roles(&element)
222+
};
223+
224+
for role in roles {
225+
acc.entry(role)
226+
.and_modify(|entry| entry.push(element.clone()))
227+
.or_insert_with(|| vec![element.clone()]);
228+
}
229+
230+
acc
231+
})
232+
}
233+
234+
#[derive(Clone, Default)]
235+
pub struct PrettyRolesOptions {
236+
pub hidden: Option<bool>,
237+
pub include_description: Option<bool>,
238+
}
239+
240+
fn pretty_roles(dom: Element, options: PrettyRolesOptions) -> String {
241+
let roles = get_roles(
242+
dom,
243+
GetRolesOptions {
244+
hidden: options.hidden,
245+
},
246+
);
247+
248+
roles
249+
.into_iter()
250+
// We prefer to skip generic role, we don't recommend it.
251+
.filter(|(role, _)| *role != AriaRoleDefinitionKey::Generic)
252+
.map(|(role, elements)| {
253+
let delimiter_bar = "-".repeat(50);
254+
let elements_string = elements
255+
.iter()
256+
.map(|element| {
257+
let name_string = format!(
258+
"Name \"{}\":\n",
259+
compute_accessible_name(element, ComputeTextAlternativeOptions::default())
260+
);
261+
262+
let dom_string = pretty_dom(
263+
Some(
264+
element
265+
.clone_node_with_deep(false)
266+
.expect("Node should be cloned.")
267+
.dyn_into::<Element>()
268+
.expect("Cloned node should be an Element.")
269+
.into(),
270+
),
271+
None,
272+
);
273+
274+
if options.include_description.unwrap_or(false) {
275+
let description_string = format!(
276+
"Description \"{}\":",
277+
compute_accessible_description(
278+
element,
279+
ComputeTextAlternativeOptions::default()
280+
)
281+
);
282+
283+
format!("{name_string}{description_string}{dom_string}")
284+
} else {
285+
format!("{name_string}{dom_string}")
286+
}
287+
})
288+
.collect::<Vec<_>>()
289+
.join("\n\n");
290+
291+
format!("{role}:\n\n{elements_string}\n\n{delimiter_bar}")
292+
})
293+
.collect::<Vec<_>>()
294+
.join("\n")
198295
}
199296

200-
pub fn log_roles() {
201-
todo!()
297+
pub fn log_roles(dom: Element, options: PrettyRolesOptions) {
298+
log::info!("{}", pretty_roles(dom, options));
202299
}
203300

204301
pub fn compute_aria_selected(element: &Element) -> Option<bool> {

0 commit comments

Comments
 (0)