-
-
Notifications
You must be signed in to change notification settings - Fork 128
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
Improve the menubar accessibility #465
Improve the menubar accessibility #465
Conversation
Thanks for submitting your first pull request! You are awesome! 🤗 |
I've added tests and the PR is now ready for review. |
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'm so stoked to see this PR, thanks @scmmmh!
Is there any way we could correct the asymmetry between tabbing forward versus backward (shift-tabbing)? Right now, what I'm seeing with your changes is that when I tab through the example page, it focuses on the skip link first, then the File menu item, then the text area. But starting from the text area and shift-tabbing backwards, I get: File menu item, then the entire menubar, then the skip link. Tabbing forward, the user does not see the menubar receive focus, but tabbing backwards they do. Does my description make sense? I can make a video if it's not clear, just lemme know! 😃
I think this is a step in the right direction, but ultimately I want to make the Lumino menubar behave more like the ARIA example menubar. There are some differences between that implementation and the Lumino menubar after this PR, which I would like to correct, for example:
- Pressing the escape key takes the user out of the menubar in Lumino, whereas in the ARIA example, it closes any open menus but keeps them in the menubar. (Tab key is how to get out.)
- Pressing the tab key while inside an open Lumino menu does nothing, whereas in the ARIA example it navigates you out of the menubar.
- When you leave the Lumino menubar it forgets the last menu item that you were on, whereas the ARIA example remembers.
And so on and so forth.
But I think this PR incrementally moves in a direction aligned with a future better UX, so I don't think it needs to be held back by any of those outstanding discrepancies.
The only reason I'm not approving it right now is because I have some questions about the code. I also cannot review the version numbering stuff in the package.json file. @afshin could you take a look at that or ping somebody who knows how to review it?
bar.dispose(); | ||
}); | ||
|
||
it('should loose focus on tab key', () => { |
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.
it('should loose focus on tab key', () => { | |
it('should lose focus on tab key', () => { |
bar.dispose(); | ||
}); | ||
|
||
it('should loose focus on shift-tab key', () => { |
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.
it('should loose focus on shift-tab key', () => { | |
it('should lose focus on shift-tab key', () => { |
or is "loose" the British way of spelling it?
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.
😆 No, this is just the one spelling mistake that I continue to make. I've always misspelled that and it looks like I always will.
node.appendChild(h1); | ||
const label = document.createElement('label'); | ||
label.appendChild( | ||
document.createTextNode('A textarea to demonstrate the tab handling.') |
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.
Could you add some more prose explaining the inclusion of the textarea on the page? I'm afraid that in the future without a better explanation, it's not going to be quite clear why the textarea is on the page, or what purpose it serves.
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'll add a comment. It is basically there so that there is some content that can be tabbed to. Otherwise the tab out of the menubar takes you into the browser UI and that can often be a bit harder to see where it has gone (I guess an a11y issue in itself).
packages/widgets/src/menubar.ts
Outdated
if (kc === 9) { | ||
if (!event.shiftKey) { | ||
this.activeIndex = -1; |
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.
Sorry, even with the comment I'm still not quite sure I get what this line is doing.
I thought if it's not resetting the activeIndex
, then I should be able to do the following on the example page:
Tab
to the menu bar- Right arrow key over to Edit (it's highlighted now)
- Click on the text area input ("Edit" is still highlighted)
- Press
Shift
+Tab
and...
Expected behavior: "Edit" remains highlighted and if I pressEnter
it should open the Edit menu.
Actual behavior: the File menu items becomes highlighted and if I pressEnter
it opens the File menu.
packages/widgets/src/menubar.ts
Outdated
/** | ||
* Handle the `'focus'` event for the menu bar. | ||
*/ | ||
private _evtFocus(event: FocusEvent): void { |
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 do we wish to reset the active menu item when the menu bar receives focus?
I would like to make the Lumino menubar behave more like the example menubar in the ARIA Authoring Practices Guide.
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 is caused by the interpretation of what it means for the "menubar to receive focus", as I put in the main reply. I'll switch to the interpretation used in the example, which will also make the code much simpler.
expect( | ||
bar.contentNode.contains(document.activeElement) && | ||
bar.contentNode !== document.activeElement | ||
).to.equal(true); |
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'm not sure this test actually tests that the menu bar loses focus. Perhaps the test should be renamed, or perhaps these 4 lines should be modified and moved below line 803 (the event dispatch).
expect( | |
bar.contentNode.contains(document.activeElement) && | |
bar.contentNode !== document.activeElement | |
).to.equal(true); | |
expect(bar.node.contains(document.activeElement)).toBe(false); |
remembering the odd property that a node.contains itself, so document.activeElement.contains(document.activeElement)
will always be true I think.
bar.contentNode.contains(document.activeElement) && | ||
bar.contentNode !== document.activeElement | ||
).to.equal(true); | ||
let event = new KeyboardEvent('keydown', { keyCode: 9 }); |
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.
Did you forget the shift modifier?
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.
Yes. I was fiddling with getting the tab behaviour to work in the test and in the process of that removed too much :-).
Thanks for all the detailed feedback. I'll iterate the code. The main reason for the divergence from the menubar example is that I was following the spec, which says "when a menubar receives focus, keyboard focus is placed on the first item". Looking at the example and your comments, it has become clear to me that there is an ambiguity here, whether the "menubar" is the actual HTML element with the I agree that the long-term aim is to make the menubar work exactly like the ARIA menubar and this is just a first, incremental step towards that. I just wanted to take some first steps in the codebase and get some feedback, before I start making bigger changes. |
If somebody could add a label, then that would reduce the number of failure e-mails I receive and would be much appreciated :-). |
Done, and thanks for working on this! |
Happy to help. I'm responsible for accessibility in my department, so every issue I fix is a complaint that doesn't come to me 😃 |
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 is so great! I really love the direction this is going in!
When trying this out locally, I noticed that if I get rid of the focus index and also get rid of the line that resets the active index, I think I get the same behavior minus the inconsistency I mention in one of my inline comments.
As such, I'm marking my review as "request changes"—not because I'm sure the code needs to change but because I have questions. Thank you for your patience with the review!
if (kc === 9) { | ||
this.activeIndex = -1; |
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 introduces an inconsistency that makes me uncomfortable. The active index gets reset when you tab out of the menubar but not when you click out. On the example page, if you tab to the File menu, then click on the button, the File menu stays shaded. But if you tab to the File menu, then tab to the button, the File menu loses its shading.
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 can live with this inconsistency for now, but we will need to address it in the future.
Co-authored-by: Frédéric Collonval <fcollonval@users.noreply.github.com>
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 would be okay with merging this code as-is, but I left some inline comments with some suggested changes.
packages/widgets/src/menubar.ts
Outdated
@@ -772,6 +783,11 @@ export namespace MenuBar { | |||
*/ | |||
readonly active: boolean; | |||
|
|||
/** | |||
* The index of the item in the list of items. |
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 this description was meant for a different variable? focusable
is a boolean, not an index.
While we're at it, could we rename this variable? To be, focusable means "able to be focussed" but I think what this variable really means is more like "should be made focusable" (by setting tabindex
to 0).
What do we think about makeFocusable
or enableFocus
or enableTabIndex
or...?
I've updated the code in line with your comments. |
Do I need to do anything to make the API changes test pass? |
I believe you need to call |
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 a lot @scmmmh and thanks @gabalafou for the review
@meeseeksdev please backport to 1.x |
Oops, something went wrong applying the patch ... Please have a look at my logs. |
Happy to help. More soon, I hope. Mark |
This PR will improve accessibility issues with the menubar component, bringing it closer to being WCAG2.1 compliant. In particular it will
This is my first bit of work with the lumino codebase, so all comments, particularly around architecture are much appreciated.
I haven't added any tests yet, that is the next step.