Improve accessibility #38
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "improve-accessibility"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Web accessibility is important, and we should try to make our components accessible by default. The W3C provides an interactive example of the correct way to make an accessible toolbar; this pull request tries to integrate their suggestions into this project. It does the following:
<button>elements by default, instead of<div>elements.mousedownevent on these buttons have changed to handle theclickevent, instead.mousedownonly works for users that use a mouse;clickis device-independent, and also works for keyboard-based usage.aria-pressedfor active MenuItems,aria-checkedfor active items in a Dropdown, etc.tabindexto manage focus.<ol>ifoptions.orderedis set to true,<ul>otherwise.This is my first attempt to contribute to ProseMirror; I hope I'm doing it right! The
CONTRIBUTING.mdfile mentioned running a linter usingnpm run lint, but that doesn't seem to be set up for this project.Thanks for working on this. It's something that should probably have happened long ago. Though my official stance is still that this is more of a demonstration of how to implement a menu than a production-quality menu, that demonstration should at least point people towards accessibility.
Does using a key handler that reacts to space and enter also provide a way around this? Mouse users don't want their editor to lose focus when they click a toolbar button, which is the reason we were using mousedown.
It seems like the four
setFocusTo*methods could be shortened by having asetFocusToIndexhelper.Is there a pressing use case that we'd need the
oland horizontal orientation support in drop-downs for? Those seem like they might cost more code than they are worth.And can we set up the default styles so that the menu looks the way it used to? I.e. no button borders and backgrounds.
Oh also, there's an issue with keyboard focus motion not skipping hidden items.
A way to move out of a drop-down menu with keyboard (I guess with escape) would probably be good to have.
Turns out the
eventobject passed to aclickevent handler has adetailattribute that counts mouse clicks. We can use that to determine whether the event was triggered by the keyboard (0 clicks) or the mouse (1 or more clicks). I've added a workaround so that when a mouse user clicks a toolbar button, the focus is immediately restored to the editor afterspec.run()is finished.This is not quite the same behavior as before. Previously, the editor stayed focused consistently; with this change, the editor loses focus when the toolbar button is clicked, but immediately regains focus afterwards. I would argue that the new behavior is actually more correct -- but I'm not sure how much that matters. I don't see a way to keep the previous behavior while making this properly accessible.
Good call, on both counts! This ended up being rather finicky to implement correctly, and I had to write not just a
setFocusToIndex()method, but also amakeContentIndexMover()method to make a function that could determine which index to move to, based on which items are hidden. I also had to (almost) duplicate this code three times: once forMenuBarView, once forDropdown, and once forDropdownSubmenu. The three are not quite the same, because of small differences in how those three classes are implemented. I could maybe try to refactor this into a single standalone class and make these three classes inherit from it -- but that's an even bigger change, and this pull request is getting rather large and complicated already. I decided to leave the duplicated code as-is, and let you decide what is the best way to deal with the situation.I figured that
olsupport would be very useful for headings (h1toh6). I don't have a pressing use case for horizontal orientation support, but I could easily imagine people wanting this feature -- and since it wasn't hard to make the keybindings dynamically based on the orientation, I figured I would include it. If you really want to take it out, we can, but I'd prefer to leave it in.I believe I've fixed this, but I haven't done extensive browser testing. Please let me know if I missed anything.
Unfortunately, that won't do. As you notice, it adds superfluous focus/blur churn, but also, if the button for example opens a dialog (to prompt for a link, say), and focuses that, forcing focus to the editor is not appropriate. We really do need to avoid the focus moving in the first place.
I thought about this a little more, and realized something very simple and obvious: it's the
mousedownevent that causes the focus to change, we can prevent that from happening by callingevent.preventDefault(), andmousedownis only triggered by the mouse (so doing this won't interfere with keyboard-based interactions). Fixing the focus problem was as simple as adding this one line:Thanks for pushing me to think a little harder about this; I don't know why I didn't think of it before!
Thanks! I've merged this as
e4cd7082cd, and followed up with373628e31dto simplify some of the code and address a few minor issues. Could you take a look and see if the revised version still works for you?It seems to work great! I've found only one very minor bug; and it may have been in my version of the code as well: re-entering a submenu resets the focus to the first element, but does not reset the focusIndex, so the focus jumps unexpectedly when pressing the down arrow.
To reproduce this, use the dev editor available from running
bin/pm dev-start. Use the keyboard to shift your focus to the toolbar, and use the arrow keys to move to the "Type..." dropdown. Use the keyboard to focus the "Heading" item, and press the right arrow to open the submenu and focus "Level 1". Press the down arrow twice, so that "Level 3" is now focused. Press the left arrow to close the submenu, and then the right arrow to open it again; "Level 1" is now focused once again. However, at this point, if you press the down arrow, the item that recieves focus is not "Level 2", as you might expect, but "Level 4"!Thanks for catching that. Patch
661ec54should help.Great! I found two more issues. One I was able to solve with this patch: #39. The other is more tricky to solve.
I noticed that when the "Heading" submenu is open, none of the items receive
aria-checked="true". When the cursor is on a heading in the editor, that item in the submenu is correctly disabled, but thearia-checkedattribute remains set to "false". I tracked this down to theupdatefunction inside therendermethod ofMenuItem: if the menu item is not enabled, then theactivemethod is never called, and it is assumed that the menu item is not active.Is this an overzealous optimization, or is there a reason why the code is like this? For radio buttons in general, it should be possible for one item to be both active and disabled simultaneously: it doesn't make sense to set a "heading 1" to "heading 1" a second time, but that doesn't change the fact that it is a "heading 1" currently, and the radio button is active.
Since these dropdowns are defined as generic all-purpose menus, not strictly radio controls, I'm starting to feel that
menuitemmight a better role thanmenuitemradio. That would also give us at lease one less tricky attribute to manage. Does that sound reasonable?For the general case, that makes sense -- although then it makes sense to allow callers to specify an option to set options in the dropdown to
menuitemcheckboxormenuitemradio, instead of the default.However, I'll remind you that you wanted to take out the
orientationoption that I originally included, because we didn't have a pressing use case for it. There is a pressing use case for setting these items tomenuitemradio: they help users with assistive technology better understand the structure of the interface they are using, in this specific case (i.e. selecting a block type for the text that is currently selected in the editor).I'm fine with making
menuitemthe default role for dropdown items, but I do feel that there should be a way for callers to specify that it should bemenuitemradioinstead, and if so, the dropdown should handle settingaria-checkedappropriately.Another issue I noticed is that picking an item from a drop-down with the keyboard will not close the drop-down. This is especially jarring with menu items that open a dialog, moving the focus away from the menu but leaving it open.
I'm not sure whether the proper solution is to always close all open menus when an item is activated, or to do focus tracking in order to only do this when the action moves focus away.
I discovered earlier today that apparently,
EnterandSpaceare suppose to do subtly different things on amenuitemradiorole:Entershould cause the menu to close, butSpaceshould not. See MDN reference here. I'm still learning about web accessibility: there's a lot of depth here!Considering that, this is another reason why it makes sense to have the dropdown options use the
menuitemrole by default.EnterandSpaceare supposed to behave identically formenuitem.I've pushed
0d374f45aaandb9abb22ed9. The latter follows Quill and Google Docs in refocusing the editor when a menu item is picked while focus is on that item, and makes closing of menus a bit more robust, autoclosing them when they lose focus, so that even if no click events are involved, they don't stick around open.Looks good to me! Could we get a new release of this project on NPM, so people can start using this more accessible code?
Let's. I've tagged 1.3.0.
Thank you, @marijnh! 🙏
Actually, could you please re-deploy the prosemirror.net website, to use the updated code? I don't think anything needs to change in the
prosemirror/websiterepo, I think it just needs to be re-deployed.Should be up to date now.
Pull request closed