We need a way to trap the focus inside a piece of DOM (or outside a piece of DOM), wrapping or refusing to tab in/out.
This includes the task of writing utility functions for detecting focusable elements.
@marcysutton do you have an input on a general focus-trapping utility?
One question I have is whether trapped focus should wrap (forward and backward) in the trapped region, or whether it should just stick to the beginning / end.
That should be a type of trapping.
What I had in mind was something akin to:
<div md-focus-trap="wrap">
<!-- ... -->
</div>
or
<div md-focus-trap="nowrap">
<!-- ... -->
</div>
Whether the default would be one or the other doesn't matter much. WDYT?
Providing options is nice in an API, however I don't think it's appropriate in this case. The convention is to wrap focus in modal dialogs, and it's what a keyboard or screen reader user would expect. It's also a WAI-ARIA best practice. https://www.w3.org/TR/wai-aria-practices/#modal_dialog
A better question is whether to trap focus inside of the dialog, or allow it to get to the browser chrome. I have heard users say they prefer it to truly be trapped in the dialog (no browser controls).
Here is part of an implementation by @rodneyrehm you can reference: https://github.com/medialize/ally.js/blob/master/src/maintain/tab-focus.js
Unless you're envisioning a keyboard trap for some other purpose than a dialog? I am curious to hear....I still think stopping a keyboard user at the end of an arbitrary tab sequence sounds like a bad idea.
We need a way to trap the focus inside a piece of DOM (or outside a piece of DOM), wrapping or refusing to tab in/out.
Do you need to trap / prevent focus for Tab and Shift Tab only, does this also apply to mouse, touch and other means of shifting focus (e.g. spatial navigation)?
Instead of trapping focus by intercepting user input, you want to simply disable all the elements that should not receive focus at a given time. ally.maintain.disabled can help with that.
This includes the task of writing utility functions for detecting focusable elements.
I've done the research on what is focusable (1.5MB) and turned that into code (e.g. ally.query.focusable, MIT licensed).
One question I have is whether trapped focus should wrap (forward and backward) in the trapped region, or whether it should just stick to the beginning / end.
Most dialog implementations I've come across wrap the focus from last to first and vice versa. But most of the implementations I've seen consider the tabsequence to equal DOM order. However, the tabindex attribute, Flexbox order property in Firefox, Image Maps and Shadow DOM do not agree with that assumption. With ally.query.tabsequence you have a simple query interface accounting for these things.
Aside from Dialog, I think focus trapping is also done in some menus and most dropdowns.
@hansl we may want to bump this to alpha.2
While chatting with another Googler, he mentioned the technique of temporarily setting everything outside the trapped region to tabIndex = -1 as being the only thing that truly works on all environments. We should look into this.
[鈥 setting everything outside the trapped region to
tabIndex = -1as being the only thing that truly works on all environments.
Except for <video controls> and <audio controls> in Blink, and SVG elements in Gecko and Trident, as well as a few other rather unlikely edge cases.
@rodneyrehm do you have any further materials on that technique? I've only heard about it anecdotally.
@jelbourn if by "that technique" you mean "setting negative tabindex to remove elements from the document's tabbing order", see ally.maintain.disabled() for a simple API to do just that (including MutationObserver and support for Shadow DOM). That API uses ally.element.disabled() which modifies elements in such a way that they're not focusable anymore (by keyboard, mouse and script).
Other than the inert-polyfill I have not found much on this subject, which is why I ran the tests and created the library. Maybe you can point me to your fellow googler so we might swap war stories?
@rodneyrehm thanks, that is useful. The person I heard this from also heard it second hand, but didn't remember from whom _he_ had heard it.
Hi. To clarify what we mean by "focus", yes focus using tab, shift-tab needs to be trapped within a dialog. However, for components like menus or radio buttons, it's not focus that is trapped, but option selection (using up/down and right/left arrow keys). Do we want 1 utility that covers focus _and_ option selection? To me they seem like 2 different things.
@kbae00 For this issue, we're just talking about actual browser focus via Tab and Shift + Tab (i.e., document.activeElement)
Closing this since we have an initial implementation, for which further enhancements will be tracked in their own issues.
jelbourn, what is the initial implementation (or suggested solution)? I need a way to prevent user from clicking outside of the mat-dialog, including mouse clicks, and keep tabbing inside the mat-tab dialog. Thanks
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
_This action has been performed automatically by a bot._
Most helpful comment
Providing options is nice in an API, however I don't think it's appropriate in this case. The convention is to wrap focus in modal dialogs, and it's what a keyboard or screen reader user would expect. It's also a WAI-ARIA best practice. https://www.w3.org/TR/wai-aria-practices/#modal_dialog
A better question is whether to trap focus inside of the dialog, or allow it to get to the browser chrome. I have heard users say they prefer it to truly be trapped in the dialog (no browser controls).
Here is part of an implementation by @rodneyrehm you can reference: https://github.com/medialize/ally.js/blob/master/src/maintain/tab-focus.js