1use std::sync::LazyLock;
7
8use indexmap::IndexMap;
9use serde::Deserialize;
10
11use crate::generated::catalog as g;
12
13#[derive(Debug, Clone, PartialEq, Deserialize)]
14pub struct ComponentA11y {
15 pub role: String,
17 #[serde(default)]
19 pub keys: Vec<String>,
20}
21
22#[derive(Debug, Clone, PartialEq, Deserialize)]
23pub struct A11yCommand {
24 pub args: Vec<String>,
25 pub description: String,
26}
27
28#[derive(Debug, Clone, Deserialize)]
29pub struct A11yContract {
30 pub rules: Vec<String>,
31 pub commands: IndexMap<String, A11yCommand>,
32 pub components: IndexMap<String, ComponentA11y>,
33}
34
35pub static A11Y: LazyLock<A11yContract> = LazyLock::new(|| serde_json::from_str(g::A11Y_JSON).expect("a11y.json"));
36
37pub fn component_a11y(name: &str) -> Option<&'static ComponentA11y> {
39 A11Y.components.get(name)
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn every_core_component_has_a_role_and_the_four_commands_exist() {
48 for name in g::COMPONENT_NAMES {
49 assert!(component_a11y(name).is_some_and(|a| !a.role.is_empty()), "{name}");
50 }
51 assert_eq!(A11Y.commands.keys().map(String::as_str).collect::<Vec<_>>(), ["focus", "announce", "scrollIntoView", "scrollToIndex"]);
52 assert!(!A11Y.rules.is_empty());
53 }
54}