freya_core/accessibility/
focus.rs1use keyboard_types::{
2 Key,
3 Modifiers,
4 NamedKey,
5};
6
7use crate::{
8 accessibility::id::AccessibilityId,
9 integration::{
10 ACCESSIBILITY_ROOT_ID,
11 AccessibilityGenerator,
12 },
13 lifecycle::reactive::use_reactive,
14 platform::{
15 NavigationMode,
16 Platform,
17 },
18 prelude::{
19 AccessibilityFocusStrategy,
20 KeyboardEventData,
21 Memo,
22 ScreenReader,
23 UserEvent,
24 consume_root_context,
25 use_hook,
26 use_memo,
27 },
28};
29
30pub trait AccessibilityIdExt {
51 fn is_focused(&self) -> bool;
53
54 fn request_focus(&self);
56
57 fn request_unfocus(&self);
59
60 fn new_unique() -> AccessibilityId;
62}
63
64impl AccessibilityIdExt for AccessibilityId {
65 fn is_focused(&self) -> bool {
66 let platform = Platform::get();
67 *platform.focused_accessibility_id.read() == *self
68 }
69
70 fn request_focus(&self) {
71 let platform = Platform::get();
72
73 if *platform.focused_accessibility_id.peek() != *self {
74 Platform::get().send(UserEvent::FocusAccessibilityNode(
75 AccessibilityFocusStrategy::Node(*self),
76 ));
77 }
78 }
79
80 fn request_unfocus(&self) {
81 let platform = Platform::get();
82
83 if *platform.focused_accessibility_id.peek() == *self {
84 Platform::get().send(UserEvent::FocusAccessibilityNode(
85 AccessibilityFocusStrategy::Node(ACCESSIBILITY_ROOT_ID),
86 ));
87 }
88 }
89
90 fn new_unique() -> Self {
91 let accessibility_generator = consume_root_context::<AccessibilityGenerator>();
92 AccessibilityId(accessibility_generator.new_id())
93 }
94}
95
96pub fn use_a11y() -> AccessibilityId {
98 use_hook(AccessibilityId::new_unique)
99}
100
101#[derive(Clone, Copy, Debug, PartialEq)]
103pub enum Focus {
104 Not,
106 Pointer,
108 Keyboard,
110}
111
112impl Focus {
113 pub fn is_focused(&self) -> bool {
115 matches!(self, Self::Pointer | Self::Keyboard)
116 }
117}
118
119pub trait KeyboardEventExt {
121 fn is_press_event(&self) -> bool;
124}
125
126impl KeyboardEventExt for KeyboardEventData {
127 fn is_press_event(&self) -> bool {
128 let is_space = matches!(self.key, Key::Character(ref s) if s == " ");
129 let is_enter = self.key == Key::Named(NamedKey::Enter);
130
131 if cfg!(target_os = "macos") {
132 let screen_reader = ScreenReader::get();
133 if screen_reader.is_on() {
134 is_space
135 && self.modifiers.contains(Modifiers::CONTROL)
136 && self.modifiers.contains(Modifiers::ALT)
137 } else {
138 is_enter || is_space
139 }
140 } else {
141 is_enter || is_space
142 }
143 }
144}
145
146pub fn use_focus(a11y_id: AccessibilityId) -> Memo<Focus> {
162 let id = use_reactive(&a11y_id);
163 use_memo(move || {
164 let platform = Platform::get();
165 let is_focused = *platform.focused_accessibility_id.read() == id();
166 let is_keyboard = *platform.navigation_mode.read() == NavigationMode::Keyboard;
167
168 match (is_focused, is_keyboard) {
169 (true, false) => Focus::Pointer,
170 (true, true) => Focus::Keyboard,
171 _ => Focus::Not,
172 }
173 })
174}