Check the CodeSandbox example linked below. When opening the Dialog by clicking Show Dialog the button in it gets focussed (so far so good). Now use the mouse to return focus to the CodeSandbox and try to type something. As soon as you begin, the focus is returned to the button in the dialog.
The problem in our setup is a bit more complex as we're opening a Google Driver Picker from a dialog but the underlying problem seems to be the same: The focus lock on the dialog is too aggressive.
The focus lock does not interfere with frame boundaries (ie. doesn't steal focus from iframes or the like).
At first I wanted to propose to make this configurable through a prop like disableFocusLock. However, given the extreme example of the CodeSandbox being rendered unusable maybe there is a more holistic solution to this. Maybe this is even an issue in react-focus-lock?
Happy to help implement something after a pointer in the right direction 馃檶
| Software | Name(s) | Version |
| ---------------- | ------- | ------- |
| Reach Package | @reach/dialog | latest (0.10.0) |
| React | | 16.12.0 |
| Browser | | Chrome |
_Edit: This might be related to #83_
Interestingly this isn't an issue in Firefox 馃 but this seems like a good use for FocusLock's whiteList prop. I'm not sure that simply exposing it directly via the DialogOverlay is the right API for us. See line 142 in Dialog.tsx: https://codesandbox.io/s/whitelist-focus-lock-for-reachdialog-8nmb1
@theKashey Have you any thoughts? I wasn't around back when Ryan implemented react-focus-lock so I'm still learning some of the nuances in its API.
I've experienced exactly this codesandbox issue during focus-lock development - while it is active the rest of application is basically 馃拃dead馃拃.
And it is a bug.
There are a few moments:
The sequence is the following:
body to be an active elementonBlur event on the document, which sets focusWasOutsideWindow=truebody is active, and focusWasOutsideWindow is true - this means what we reactivated current tab, and __focus has to be restored__.If focusWasOutsideWindow would be false - no focus stealing would occur, and the problem here is that the page is still the same... and React elements receives blur as well and triggers the check.
And here is the problem - document.onBlur sets focusWasOutsideWindow synchronously, while trap.onBlur works asynchronously(Promise + setImmediate). If only the would be in the same conditions - focusWasOutsideWindow would not be set by the time onBlur handler is called.
Should be easy to fix the problem, as well as create a test for it. Thanks for calling me 馃憤
Even better, and thanks for the detailed explanation @theKashey!
I am adding new crossFrame option, right now defaults to true, as long as this behaviour might introduce a breaking change for someone.
crossFrame=true will work "as today". Focus will __try to keep focus__crossFrame=false will work "as proposed". Iframes would be treated as "pages", and let you move focus outside.I am not sure which behaviour is actually correct - iframes are usually a part of a page, a part of your product, and the only exemptions are non-production examples like code-sandbox.
Said option was added in [email protected]
@henningmu See https://github.com/reach/reach-ui/commit/de0655b2de8698f5e7add2e4507dee5062750b36#diff-ca1b0a6f0c85b5110ece085b9f18ca0bR304
Nice one @chancestrickland (and @theKashey) that fixes our problem, thank you very much 馃帀 馃檶
Added the unstable_lockFocusAcrossFrames prop in 0.10.1
Most helpful comment
I am adding new
crossFrameoption, right now defaults to true, as long as this behaviour might introduce a breaking change for someone.crossFrame=truewill work "as today". Focus will __try to keep focus__crossFrame=falsewill work "as proposed". Iframes would be treated as "pages", and let you move focus outside.I am not sure which behaviour is actually correct -
iframesare usually a part of a page, a part of your product, and the only exemptions are non-production examples likecode-sandbox.