use super::{Constraint, ConstraintContext, SpecReference, Violation, WcagLevel};
use accesskit::{NodeId, Role};
fn requires_label(role: Role) -> bool {
matches!(
role,
Role::Button
| Role::Link
| Role::CheckBox
| Role::RadioButton
| Role::TextInput
| Role::Slider
| Role::SpinButton
| Role::MenuItem
| Role::MenuItemCheckBox
| Role::MenuItemRadio
| Role::Tab
| Role::Switch
| Role::Search
| Role::ComboBox
)
}
fn is_focusable_role(role: Role) -> bool {
matches!(
role,
Role::Button
| Role::Link
| Role::CheckBox
| Role::RadioButton
| Role::TextInput
| Role::Slider
| Role::SpinButton
| Role::MenuItem
| Role::MenuItemCheckBox
| Role::MenuItemRadio
| Role::Tab
| Role::Switch
| Role::Search
| Role::ComboBox
)
}
#[derive(Debug, Clone, Copy)]
pub struct HasLabelConstraint;
impl Constraint for HasLabelConstraint {
fn check(&self, node_id: NodeId, ctx: &ConstraintContext<'_>) -> Result<(), Violation> {
let node = match ctx.nodes.get(&node_id) {
Some(n) => n,
None => return Ok(()),
};
if !requires_label(node.role()) {
return Ok(());
}
match node.label() {
Some(name) if !name.is_empty() => Ok(()),
Some(_) => Err(Violation::EmptyLabel {
element: node_id.into(),
}),
None => Err(Violation::MissingLabel {
element: node_id.into(),
}),
}
}
fn spec_ref(&self) -> SpecReference {
SpecReference::Wcag {
criterion: "4.1.2",
level: WcagLevel::A,
url: "https://www.w3.org/WAI/WCAG22/Understanding/name-role-value",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ValidRoleConstraint;
impl Constraint for ValidRoleConstraint {
fn check(&self, node_id: NodeId, ctx: &ConstraintContext<'_>) -> Result<(), Violation> {
let _node = ctx.nodes.get(&node_id);
Ok(())
}
fn spec_ref(&self) -> SpecReference {
SpecReference::Wcag {
criterion: "4.1.2",
level: WcagLevel::A,
url: "https://www.w3.org/WAI/WCAG22/Understanding/name-role-value",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MinTouchTargetConstraint;
impl Constraint for MinTouchTargetConstraint {
fn check(&self, node_id: NodeId, ctx: &ConstraintContext<'_>) -> Result<(), Violation> {
let node = match ctx.nodes.get(&node_id) {
Some(n) => n,
None => return Ok(()),
};
if !is_focusable_role(node.role()) {
return Ok(());
}
if let Some(bounds) = node.bounds() {
let width = bounds.width() as u32;
let height = bounds.height() as u32;
if width >= 44 && height >= 44 {
Ok(())
} else {
Err(Violation::TouchTarget {
element: node_id.into(),
actual_width: width,
actual_height: height,
min_dimension: 44,
})
}
} else {
Ok(())
}
}
fn spec_ref(&self) -> SpecReference {
SpecReference::Wcag {
criterion: "2.5.5",
level: WcagLevel::AAA,
url: "https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct NoOverflowConstraint;
impl Constraint for NoOverflowConstraint {
fn check(&self, node_id: NodeId, ctx: &ConstraintContext<'_>) -> Result<(), Violation> {
let node = match ctx.nodes.get(&node_id) {
Some(n) => n,
None => return Ok(()),
};
if let Some(bounds) = node.bounds() {
let x = bounds.x0 as i32;
let y = bounds.y0 as i32;
let width = bounds.width() as u32;
let height = bounds.height() as u32;
let fits_horizontally = x >= 0 && (x as u32 + width) <= ctx.viewport.width;
let fits_vertically = y >= 0 && (y as u32 + height) <= ctx.viewport.height;
if fits_horizontally && fits_vertically {
Ok(())
} else {
Err(Violation::Overflow {
element: node_id.into(),
element_x: x,
element_y: y,
element_width: width,
element_height: height,
viewport_width: ctx.viewport.width,
viewport_height: ctx.viewport.height,
})
}
} else {
Ok(())
}
}
fn spec_ref(&self) -> SpecReference {
SpecReference::Wcag {
criterion: "1.4.10",
level: WcagLevel::AA,
url: "https://www.w3.org/WAI/WCAG22/Understanding/reflow",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct KeyboardAccessibleConstraint;
impl Constraint for KeyboardAccessibleConstraint {
fn check(&self, node_id: NodeId, ctx: &ConstraintContext<'_>) -> Result<(), Violation> {
let node = match ctx.nodes.get(&node_id) {
Some(n) => n,
None => return Ok(()),
};
let _is_focusable = is_focusable_role(node.role());
Ok(())
}
fn spec_ref(&self) -> SpecReference {
SpecReference::Wcag {
criterion: "2.1.1",
level: WcagLevel::A,
url: "https://www.w3.org/WAI/WCAG22/Understanding/keyboard",
}
}
}