-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AccessKit integration #78
Conversation
@matthunz Have you tried actually using this with a screenreader? |
packages/dioxus-blitz/src/window.rs
Outdated
#[cfg(feature = "accesskit")] | ||
adapter: accesskit_winit::Adapter::with_event_loop_proxy(&window, proxy.clone()), | ||
#[cfg(feature = "accesskit")] | ||
node_ids: Default::default(), | ||
#[cfg(feature = "accesskit")] | ||
id_to_node_ids: Default::default(), | ||
#[cfg(feature = "accesskit")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps these fields ought to be grouped together in a struct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sure, I like this a lot, those cfg
flags are super ugly
blitz/packages/dioxus-blitz/src/accessibility.rs
Lines 7 to 13 in 0ce74dd
pub struct AccessibilityState { | |
/// Adapter to connect to the [`EventLoop`](`winit::event_loop::EventLoop`). | |
adapter: accesskit_winit::Adapter, | |
/// Next ID to assign an an [`accesskit::Node`]. | |
next_id: u64, | |
} |
packages/dioxus-blitz/src/window.rs
Outdated
#[cfg(feature = "accesskit")] | ||
node_ids: std::collections::HashMap<accesskit::NodeId, usize>, | ||
|
||
#[cfg(feature = "accesskit")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps the "accesskit id" ought to be a field on the node itself? Every node will have one, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me but would we need to move accesskit
to Blitz core for that? For now I removed the field until we have support for accessibility events (that doesn't seem clear to me to implement yet unless you have any ideas 🤔 )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but would we need to move accesskit to Blitz core for that?
I don't think so? Node IDs are just usize atm, and are accessible from outside of the crate. We'll might want to switch to a newtype at some point, but I'm sure we'll make conversions to plain types available.
packages/dioxus-blitz/src/window.rs
Outdated
#[cfg(feature = "accesskit")] | ||
id_to_node_ids: std::collections::HashMap<usize, accesskit::NodeId>, | ||
|
||
#[cfg(feature = "accesskit")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think all of these new fields ought to have comments documenting what they are/do.
packages/dioxus-blitz/src/window.rs
Outdated
let name = element_data.name.local.to_string(); | ||
let role = match &*name { | ||
"button" => accesskit::Role::Button, | ||
"div" | "section" => accesskit::Role::Group, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AccessKit docs suggest that div
ought to be Role::GenericContainer
(https://docs.rs/accesskit/latest/accesskit/enum.Role.html#variant.GenericContainer)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like there's a bunch that could easily be plugged in here (e.g. h1, h2 should have Heading
role). Happy to defer that until later, but could we at least get a TODO in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the tips 👍 I added those and some others I could think of here so far
blitz/packages/dioxus-blitz/src/accessibility.rs
Lines 62 to 78 in 0ce74dd
// TODO match more roles | |
let role = match &*name { | |
"button" => Role::Button, | |
"div" => Role::GenericContainer, | |
"header" => Role::Header, | |
"h1" | "h2" | "h3" | "h4" | "h5" | "h6" => Role::Heading, | |
"p" => Role::Paragraph, | |
"section" => Role::Section, | |
"input" => { | |
let ty = element_data.attr(local_name!("type")).unwrap_or("text"); | |
match ty { | |
"number" => Role::NumberInput, | |
_ => Role::TextInput, | |
} | |
} | |
_ => Role::Unknown, | |
}; |
packages/dioxus-blitz/src/window.rs
Outdated
@@ -308,6 +398,47 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { | |||
self.waker = None; | |||
self.renderer.suspend(); | |||
} | |||
|
|||
#[cfg(feature = "accesskit")] | |||
fn build_accessibility_tree(&mut self) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this function (and probably build_node
above too) into their own accesskit
module?
@@ -87,6 +92,8 @@ pub struct Document { | |||
pub(crate) layout_ctx: parley::LayoutContext<TextBrush>, | |||
|
|||
pub(crate) hover_node_id: Option<usize>, | |||
|
|||
pub changed: HashSet<usize>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a HashSet
rather than a Vec
? It never seems to be randomly queried, only ever iterated over?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My plan was to eventually have this be used for the fine-grained accessibility tree updates but I don't see clear path for that yet 🤔 we could totally switch this to a bool
for now if we want to stick with the coarse updates for now.
Personally I think we can get away with coarse-grained for a little while before apps in Blitz start getting overly complex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, HashSet
for deduplicating insertions. Makes sense.
packages/dioxus-blitz/src/window.rs
Outdated
// TODO send fine grained accessibility tree updates. | ||
let changed = mem::take(&mut self.renderer.dom.as_mut().changed); | ||
if !changed.is_empty() { | ||
self.build_accessibility_tree() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like it wouldn't be too hard to send a TreeUpdate
with just changed nodes here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right this is how we should handle this, I was just having some trouble getting the hierarchy working. The issue I had was creating IDs for the accesskit elements so maybe re-using the Blitz element IDs here like you mentioned could help?
packages/dioxus-blitz/src/window.rs
Outdated
node_builder.set_name(node.text_content()); | ||
} | ||
|
||
let id = accesskit::NodeId(self.next_id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh... we get to choose the AccessKit ids... Is there any reason not to use the dom's NodeId directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are able to map 1-1 from nodes (and text nodes) I'd say we should totally do this 👀
accesskit_winit::WindowEvent::ActionRequested(_req) => { | ||
// TODO | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might it be worth at least implementing "click" here (which of course is the only event we support generally. I think the code could just be copied from the regular click handler (skipping the bit which determines which node to generate the event for as we should be given a node id directly)
Cargo.toml
Outdated
euclid = { version = "0.22", features = ["serde"] } | ||
# mozbuild = "0.1.0" | ||
|
||
[dev-dependencies] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for moving all of these deps from "dev-dependencies" to "dependencies"?
Yeah it looks to be reading the elements OK so far, I posted this video in the Dioxus discord a little while back if you missed it dioxus2.mp4 |
}; | ||
|
||
node_builder.set_role(role); | ||
node_builder.set_name(name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this scope, the variable name
seem to always contain the "HTML element type" which is not something the user should see. You could call NodeBuilder::set_html_tag
(although we currently don't expose this property to assistive technologies) but you should not use it for the AccessKit node name.
Hello @matthunz, AccessKit dev here. I've tried a modified version of the accessibility example to add more kind of nodes and my screen reader is already able to pick them up. |
Hey @DataTriny thanks for your insight! I switched I also tried added some basic I'd also really like to connect to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to accept this as an initial implementation and iterate :)
Starts work on AccessKit integration (#14)
accesskit-winit
adapterDom::visit
to traverse the dom top-downI think it's best to wait for more interactivity and focus system design before processing AccessKit events with the DOM 🤔 totally open to trying to add that here though