Skip to content

Commit

Permalink
feat: a11y_auto_focus (#878)
Browse files Browse the repository at this point in the history
* feat: `a11y_auto_focus`

* chore: Code cleanup

* chore: clean up

* chore: Clean up and unit test
  • Loading branch information
marc2332 authored Sep 13, 2024
1 parent ef1df78 commit 13f67c5
Show file tree
Hide file tree
Showing 14 changed files with 119 additions and 51 deletions.
6 changes: 6 additions & 0 deletions crates/common/src/accessibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ use rustc_hash::{

#[derive(Default)]
pub struct AccessibilityDirtyNodes {
pub requested_focus: Option<NodeId>,
pub added_or_updated: FxHashSet<NodeId>,
pub removed: FxHashMap<NodeId, NodeId>,
}

impl AccessibilityDirtyNodes {
pub fn request_focus(&mut self, node_id: NodeId) {
self.requested_focus = Some(node_id);
}

pub fn add_or_update(&mut self, node_id: NodeId) {
self.added_or_updated.insert(node_id);
}
Expand All @@ -25,6 +30,7 @@ impl AccessibilityDirtyNodes {
}

pub fn clear(&mut self) {
self.requested_focus.take();
self.added_or_updated.clear();
self.removed.clear();
}
Expand Down
1 change: 1 addition & 0 deletions crates/components/src/hooks/use_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ impl<Id: Clone + Hash + Eq + Display> UseForm<Id> {
mode: InputMode::default(),
value,
placeholder: Some(placeholder),
auto_focus: false,
}
}

Expand Down
9 changes: 7 additions & 2 deletions crates/components/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ pub struct InputProps {
/// Display mode for Input. By default, input text is shown as it is provided.
#[props(default = InputMode::Shown, into)]
pub mode: InputMode,
/// Automatically focus this Input upon creation. Default `false`.
#[props(default = false)]
pub auto_focus: bool,
}

/// Small box to edit text.
Expand Down Expand Up @@ -96,6 +99,7 @@ pub fn Input(
onchange,
mode,
placeholder,
auto_focus,
}: InputProps,
) -> Element {
let platform = use_platform();
Expand Down Expand Up @@ -210,11 +214,12 @@ pub fn Input(
shadow: "{shadow}",
corner_radius: "{corner_radius}",
margin: "{margin}",
main_align: "center",
cursor_reference,
a11y_id,
a11y_focusable: "true",
a11y_role:"textInput",
main_align: "center",
a11y_role: "textInput",
a11y_auto_focus: "{auto_focus}",
onkeydown,
onkeyup,
paragraph {
Expand Down
30 changes: 24 additions & 6 deletions crates/core/src/accessibility/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ impl AccessibilityTree {
rdom: &DioxusDOM,
layout: &Torin<NodeId>,
dirty_nodes: &mut AccessibilityDirtyNodes,
) -> TreeUpdate {
) -> (TreeUpdate, NodeId) {
let requested_focus_id = dirty_nodes.requested_focus.take();
let removed_ids = dirty_nodes.removed.drain().collect::<FxHashMap<_, _>>();
let mut added_or_updated_ids = dirty_nodes
.added_or_updated
Expand Down Expand Up @@ -186,16 +187,33 @@ impl AccessibilityTree {
}
}

// Try to focus the requested node id
if let Some(node_id) = requested_focus_id {
let node_ref = rdom.get(node_id).unwrap();
let node_accessibility_state = node_ref.get::<AccessibilityNodeState>();
if let Some(focused_id) = node_accessibility_state
.as_ref()
.and_then(|state| state.a11y_id)
{
self.focused_id = focused_id;
}
}

// Fallback the focused id to the root if the focused node no longer exists
if !self.map.contains_key(&self.focused_id) {
self.focused_id = ACCESSIBILITY_ROOT_ID;
}

TreeUpdate {
nodes,
tree: Some(Tree::new(ACCESSIBILITY_ROOT_ID)),
focus: self.focused_id,
}
let node_id = self.map.get(&self.focused_id).cloned().unwrap();

(
TreeUpdate {
nodes,
tree: Some(Tree::new(ACCESSIBILITY_ROOT_ID)),
focus: self.focused_id,
},
node_id,
)
}

/// Update the focused Node ID and generate a TreeUpdate if necessary.
Expand Down
2 changes: 0 additions & 2 deletions crates/core/src/event_messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ pub enum EventMessage {
Accessibility(accesskit_winit::WindowEvent),
/// Focus the given accessibility NodeID
FocusAccessibilityNode(accesskit::NodeId),
/// Queue a focus the given accessibility NodeID
QueueFocusAccessibilityNode(accesskit::NodeId),
/// Focus the next accessibility Node
FocusNextAccessibilityNode,
/// Focus the previous accessibility Node
Expand Down
5 changes: 5 additions & 0 deletions crates/elements/src/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ builder_constructors! {
#[doc = include_str!("_docs/attributes/spacing.md")]
spacing: String,

a11y_auto_focus: String,
a11y_name: String,
a11y_focusable: String,
a11y_role:String,
Expand Down Expand Up @@ -305,6 +306,7 @@ builder_constructors! {
opacity: String,

layer: String,
a11y_auto_focus: String,
a11y_name: String,
a11y_focusable: String,
a11y_role:String,
Expand Down Expand Up @@ -383,6 +385,7 @@ builder_constructors! {
cursor_color: String,
cursor_mode: String,
cursor_id: String,
a11y_auto_focus: String,
a11y_name: String,
a11y_focusable: String,
a11y_role:String,
Expand Down Expand Up @@ -454,6 +457,7 @@ builder_constructors! {

image_data: String,
image_reference: String,
a11y_auto_focus: String,
a11y_name: String,
a11y_focusable: String,
a11y_role:String,
Expand Down Expand Up @@ -493,6 +497,7 @@ builder_constructors! {

svg_data: String,
svg_content: String,
a11y_auto_focus: String,
a11y_name: String,
a11y_focusable: String,
a11y_role:String,
Expand Down
9 changes: 0 additions & 9 deletions crates/hooks/src/use_focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,6 @@ impl UseFocus {
}
}

/// Queue a focus to this node
pub fn queue_focus(&mut self) {
if !*self.is_focused.peek() {
self.platform
.send(EventMessage::QueueFocusAccessibilityNode(self.id))
.ok();
}
}

/// Get the node focus ID
pub fn id(&self) -> AccessibilityId {
self.id
Expand Down
27 changes: 27 additions & 0 deletions crates/hooks/src/use_init_native_platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,31 @@ mod test {
assert_ne!(first_focus_id, second_focus_id);
assert_ne!(second_focus_id, ACCESSIBILITY_ROOT_ID);
}

#[tokio::test]
pub async fn auto_focus_accessibility() {
fn use_focus_app() -> Element {
rsx!(
rect {
a11y_role: "genericContainer",
a11y_auto_focus: "true",
}
rect {
a11y_role: "genericContainer",
a11y_auto_focus: "true",
}
)
}

let mut utils = launch_test_with_config(
use_focus_app,
TestingConfig {
size: (100.0, 100.0).into(),
..TestingConfig::default()
},
);

utils.wait_for_update().await;
assert_ne!(utils.focus_id(), ACCESSIBILITY_ROOT_ID); // Will focus the second rect
}
}
2 changes: 2 additions & 0 deletions crates/native-core/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub enum AttributeName {
PositionLeft,
Opacity,
Content,
A11YAutoFocus,
A11YName,
A11YFocusable,
A11YRole,
Expand Down Expand Up @@ -116,6 +117,7 @@ impl FromStr for AttributeName {
"position_left" => Ok(AttributeName::PositionLeft),
"opacity" => Ok(AttributeName::Opacity),
"content" => Ok(AttributeName::Content),
"a11y_auto_focus" => Ok(AttributeName::A11YAutoFocus),
"a11y_name" => Ok(AttributeName::A11YName),
"a11y_focusable" => Ok(AttributeName::A11YFocusable),
"a11y_role" => Ok(AttributeName::A11YRole),
Expand Down
13 changes: 12 additions & 1 deletion crates/renderer/src/accessibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,26 @@ impl AccessKitManager {
&mut self,
rdom: &DioxusDOM,
layout: &Torin<NodeId>,
platform_sender: &NativePlatformSender,
window: &Window,
dirty_nodes: &mut AccessibilityDirtyNodes,
) {
let tree =
let (tree, node_id) =
self.accessibility_tree
.lock()
.unwrap()
.process_updates(rdom, layout, dirty_nodes);

// Notify the components
platform_sender.send_modify(|state| {
state.focused_id = tree.focus;
});

// Update the IME Cursor area
self.update_ime_position(node_id, window, layout);

if self.adapter_initialized {
// Update the Adapter
self.accessibility_adapter.update_if_active(|| tree);
}
}
Expand Down
35 changes: 12 additions & 23 deletions crates/renderer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ pub struct Application {
pub(crate) measure_layout_on_next_render: bool,
pub(crate) init_accessibility_on_next_render: bool,
pub(crate) default_fonts: Vec<String>,
pub(crate) queued_focus_node: Option<AccessibilityId>,
}

impl Application {
Expand Down Expand Up @@ -116,7 +115,6 @@ impl Application {
measure_layout_on_next_render: false,
init_accessibility_on_next_render: false,
default_fonts,
queued_focus_node: None,
compositor: Compositor::default(),
};

Expand Down Expand Up @@ -245,7 +243,7 @@ impl Application {
)
}

pub fn init_accessibility(&mut self, window: &Window) {
pub fn init_accessibility(&mut self) {
{
let fdom = self.sdom.get();
let rdom = fdom.rdom();
Expand All @@ -254,25 +252,20 @@ impl Application {
self.accessibility
.init_accessibility(rdom, &layout, &mut dirty_accessibility_tree);
}

if let Some(node_id) = self.queued_focus_node.take() {
self.focus_node(node_id, window)
}
}

pub fn process_accessibility(&mut self, window: &Window) {
{
let fdom = self.sdom.get();
let rdom = fdom.rdom();
let layout = fdom.layout();
let mut dirty_accessibility_tree = fdom.accessibility_dirty_nodes();
self.accessibility
.process_updates(rdom, &layout, &mut dirty_accessibility_tree);
}

if let Some(node_id) = self.queued_focus_node.take() {
self.focus_node(node_id, window)
}
let fdom = self.sdom.get();
let rdom = fdom.rdom();
let layout = fdom.layout();
let mut dirty_accessibility_tree = fdom.accessibility_dirty_nodes();
self.accessibility.process_updates(
rdom,
&layout,
&self.platform_sender,
window,
&mut dirty_accessibility_tree,
);
}

/// Send an event
Expand Down Expand Up @@ -355,10 +348,6 @@ impl Application {
.focus_node(node_id, &self.platform_sender, window, &layout)
}

pub fn queue_focus_node(&mut self, node_id: AccessibilityId) {
self.queued_focus_node = Some(node_id);
}

pub fn focus_next_node(&mut self, direction: AccessibilityFocusStrategy, window: &Window) {
let fdom = self.sdom.get();
let rdom = fdom.rdom();
Expand Down
5 changes: 1 addition & 4 deletions crates/renderer/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,6 @@ impl<'a, State: Clone> ApplicationHandler<EventMessage> for DesktopRenderer<'a,
app.focus_next_node(AccessibilityFocusStrategy::Forward, window);
}
EventMessage::WithWindow(use_window) => (use_window)(window),
EventMessage::QueueFocusAccessibilityNode(node_id) => {
app.queue_focus_node(node_id);
}
EventMessage::ExitApp => event_loop.exit(),
EventMessage::PlatformEvent(platform_event) => self.send_event(platform_event),
ev => {
Expand Down Expand Up @@ -285,7 +282,7 @@ impl<'a, State: Clone> ApplicationHandler<EventMessage> for DesktopRenderer<'a,
}

if app.init_accessibility_on_next_render {
app.init_accessibility(window);
app.init_accessibility();
app.init_accessibility_on_next_render = false;
}

Expand Down
Loading

0 comments on commit 13f67c5

Please sign in to comment.