Skip to content
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

Merged
merged 20 commits into from
Nov 28, 2022

Conversation

scmmmh
Copy link
Contributor

@scmmmh scmmmh commented Nov 14, 2022

This PR will improve accessibility issues with the menubar component, bringing it closer to being WCAG2.1 compliant. In particular it will

  • Set the tabindex properties as specified;
  • Handle the focus setting as specified, both when focus first moves into the menubar and also when focus moves out of it;

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.

@welcome
Copy link

welcome bot commented Nov 14, 2022

Thanks for submitting your first pull request! You are awesome! 🤗

If you haven't done so already, check out Jupyter's Code of Conduct. Also, please make sure you followed the pull request template, as this will help us review your contribution more quickly.
welcome
You can meet the other Jovyans by joining our Discourse forum. There is also a intro thread there where you can stop by and say Hi! 👋

Welcome to the Jupyter community! 🎉

@scmmmh
Copy link
Contributor Author

scmmmh commented Nov 14, 2022

I've added tests and the PR is now ready for review.

@scmmmh scmmmh marked this pull request as ready for review November 14, 2022 19:40
@scmmmh scmmmh changed the title WIP: Improve the menubar accessibility Improve the menubar accessibility Nov 14, 2022
@gabalafou gabalafou self-requested a review November 15, 2022 18:13
Copy link
Contributor

@gabalafou gabalafou left a 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:

  1. 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.)
  2. Pressing the tab key while inside an open Lumino menu does nothing, whereas in the ARIA example it navigates you out of the menubar.
  3. 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', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it('should loose focus on tab key', () => {
it('should lose focus on tab key', () => {

bar.dispose();
});

it('should loose focus on shift-tab key', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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?

Copy link
Contributor Author

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.

packages/widgets/src/menubar.ts Show resolved Hide resolved
node.appendChild(h1);
const label = document.createElement('label');
label.appendChild(
document.createTextNode('A textarea to demonstrate the tab handling.')
Copy link
Contributor

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.

Copy link
Contributor Author

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).

examples/example-menubar/src/index.ts Show resolved Hide resolved
if (kc === 9) {
if (!event.shiftKey) {
this.activeIndex = -1;
Copy link
Contributor

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:

  1. Tab to the menu bar
  2. Right arrow key over to Edit (it's highlighted now)
  3. Click on the text area input ("Edit" is still highlighted)
  4. Press Shift + Tab and...
    Expected behavior: "Edit" remains highlighted and if I press Enter it should open the Edit menu.
    Actual behavior: the File menu items becomes highlighted and if I press Enter it opens the File menu.

/**
* Handle the `'focus'` event for the menu bar.
*/
private _evtFocus(event: FocusEvent): void {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Comment on lines 798 to 801
expect(
bar.contentNode.contains(document.activeElement) &&
bar.contentNode !== document.activeElement
).to.equal(true);
Copy link
Contributor

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).

Suggested change
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.

packages/widgets/tests/src/menubar.spec.ts Outdated Show resolved Hide resolved
bar.contentNode.contains(document.activeElement) &&
bar.contentNode !== document.activeElement
).to.equal(true);
let event = new KeyboardEvent('keydown', { keyCode: 9 });
Copy link
Contributor

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?

Copy link
Contributor Author

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 :-).

@scmmmh
Copy link
Contributor Author

scmmmh commented Nov 16, 2022

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 role="menubar" or the component as a whole. In the first interpretation the HTML element must have tabindex="0" to be focusable. As a result you have this slightly different behaviour tabbing forward and backward and also a lot of the code I have is to handle the resulting edge cases. In the second interpretation, none of that is necessary and only the menuitems are focusable. Since that is the interpretation used by the example, I'll rework the code to follow that one. It will make the code a good bit simpler, so that'll be nice.

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.

@scmmmh
Copy link
Contributor Author

scmmmh commented Nov 16, 2022

If somebody could add a label, then that would reduce the number of failure e-mails I receive and would be much appreciated :-).

@blink1073 blink1073 added the enhancement New feature or request label Nov 16, 2022
@blink1073
Copy link
Contributor

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!

@scmmmh
Copy link
Contributor Author

scmmmh commented Nov 16, 2022

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 😃

Copy link
Contributor

@gabalafou gabalafou left a 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!

examples/example-menubar/package.json Outdated Show resolved Hide resolved
packages/widgets/src/menubar.ts Outdated Show resolved Hide resolved
if (kc === 9) {
this.activeIndex = -1;
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

@gabalafou gabalafou left a 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.

@@ -772,6 +783,11 @@ export namespace MenuBar {
*/
readonly active: boolean;

/**
* The index of the item in the list of items.
Copy link
Contributor

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...?

packages/widgets/src/menubar.ts Outdated Show resolved Hide resolved
@scmmmh
Copy link
Contributor Author

scmmmh commented Nov 22, 2022

I've updated the code in line with your comments.

@scmmmh
Copy link
Contributor Author

scmmmh commented Nov 24, 2022

Do I need to do anything to make the API changes test pass?

@blink1073
Copy link
Contributor

Do I need to do anything to make the API changes test pass?

I believe you need to call yarn run api and then commit the changes.

Copy link
Member

@fcollonval fcollonval left a 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

@fcollonval fcollonval merged commit 14c3380 into jupyterlab:main Nov 28, 2022
@welcome
Copy link

welcome bot commented Nov 28, 2022

Congrats on your first merged pull request in this project! 🎉
congrats
Thank you for contributing, we are very proud of you! ❤️

@fcollonval fcollonval added this to the 1.x milestone Nov 28, 2022
@fcollonval
Copy link
Member

@meeseeksdev please backport to 1.x

@lumberbot-app
Copy link

lumberbot-app bot commented Nov 28, 2022

Oops, something went wrong applying the patch ... Please have a look at my logs.

@scmmmh
Copy link
Contributor Author

scmmmh commented Nov 28, 2022

Thanks a lot @scmmmh and thanks @gabalafou for the review

Happy to help. More soon, I hope.

Mark

@scmmmh scmmmh deleted the improve-menubar-accessibility branch November 28, 2022 11:55
fcollonval added a commit to fcollonval/lumino that referenced this pull request Nov 28, 2022
fcollonval added a commit that referenced this pull request Nov 28, 2022
* Backport #465 on branch 1.x (Improve the menubar accessibility)

* Fix tests

* Fix tests requiring bubbling
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants