Skip to main content

use_focus/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4/// Focus state metadata.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
6pub enum FocusState {
7    Unfocused,
8    Focused,
9    FocusVisible,
10    Disabled,
11}
12
13impl FocusState {
14    pub fn has_focus(self) -> bool {
15        matches!(self, Self::Focused | Self::FocusVisible)
16    }
17}
18
19/// A focus target identifier.
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
21pub struct FocusTarget(String);
22
23impl FocusTarget {
24    pub fn new(value: impl Into<String>) -> Self {
25        Self(value.into())
26    }
27
28    pub fn as_str(&self) -> &str {
29        &self.0
30    }
31}
32
33impl AsRef<str> for FocusTarget {
34    fn as_ref(&self) -> &str {
35        self.as_str()
36    }
37}
38
39/// Focus order metadata.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
41pub struct FocusOrder(usize);
42
43impl FocusOrder {
44    pub fn new(value: usize) -> Self {
45        Self(value)
46    }
47
48    pub fn value(self) -> usize {
49        self.0
50    }
51}
52
53/// Focus visibility preference.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
55pub enum FocusVisibility {
56    Auto,
57    Visible,
58    Hidden,
59}
60
61/// Platform-neutral tab index metadata.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
63pub struct TabIndex(i16);
64
65impl TabIndex {
66    pub fn new(value: i16) -> Self {
67        Self(value)
68    }
69
70    pub fn value(self) -> i16 {
71        self.0
72    }
73
74    pub fn is_focusable(self) -> bool {
75        self.0 >= 0
76    }
77}
78
79/// A named collection of focus targets.
80#[derive(Debug, Clone, Default, PartialEq, Eq)]
81pub struct FocusScope {
82    targets: Vec<FocusTarget>,
83}
84
85impl FocusScope {
86    pub fn new(targets: Vec<FocusTarget>) -> Self {
87        Self { targets }
88    }
89
90    pub fn targets(&self) -> &[FocusTarget] {
91        &self.targets
92    }
93
94    pub fn contains(&self, target: &FocusTarget) -> bool {
95        self.targets.contains(target)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::{FocusOrder, FocusScope, FocusState, FocusTarget, FocusVisibility, TabIndex};
102
103    #[test]
104    fn checks_focus_state_and_index_helpers() {
105        assert!(FocusState::FocusVisible.has_focus());
106        assert!(!FocusState::Disabled.has_focus());
107        assert!(TabIndex::new(0).is_focusable());
108        assert!(!TabIndex::new(-1).is_focusable());
109        assert_eq!(FocusOrder::new(2).value(), 2);
110        assert_eq!(FocusVisibility::Visible, FocusVisibility::Visible);
111    }
112
113    #[test]
114    fn creates_focus_scopes() {
115        let submit = FocusTarget::new("submit");
116        let cancel = FocusTarget::new("cancel");
117        let scope = FocusScope::new(vec![submit.clone()]);
118
119        assert!(scope.contains(&submit));
120        assert!(!scope.contains(&cancel));
121        assert_eq!(scope.targets().len(), 1);
122    }
123}