1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
55pub enum FocusVisibility {
56 Auto,
57 Visible,
58 Hidden,
59}
60
61#[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#[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}