-
Notifications
You must be signed in to change notification settings - Fork 70
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
Add code & demo to pause/unpause parents of nested traps #30
Add code & demo to pause/unpause parents of nested traps #30
Conversation
Thanks @chandlerprall! I'm pretty busy right now so might not get to this for a few days, but I'll keep in my todo list and try to have a close look soon. |
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.
Great work figuring this out, @chandlerprall. I have a few questions for you.
"react": "^16.0.0", | ||
"react-dom": "^16.0.0" | ||
}, | ||
"dependencies": { | ||
"focus-trap": "^3.0.0" | ||
}, | ||
"peerDependencies": { | ||
"prop-types": "^15.0.0", |
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 this absolutely necessary, or could we avoid the breaking change by providing a dummy function for the context types?
Maybe we should end up adding a PropTypes peer dependency, but I'd love to treat that as a separate issue if possible.
_FocusTrapReact: { | ||
pause: () => this.focusTrap.pause(), | ||
unpause: () => { | ||
if (this.props.paused === false) { |
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 we could leave out this condition, since focus-trap already handles it: https://github.com/davidtheclark/focus-trap/blob/master/index.js#L93
@@ -45,6 +66,10 @@ class FocusTrap extends React.Component { | |||
if (this.props.paused) { | |||
this.focusTrap.pause(); | |||
} | |||
|
|||
// if there is a _FocusTrapReact context from a parent focus trap, pause it | |||
const {_FocusTrapReact: {pause} = {}} = this.context; |
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.
Let's name this context prop something like FocusTrapParent
, to clearly indicate that we're talking about a parent.
@@ -45,6 +66,10 @@ class FocusTrap extends React.Component { | |||
if (this.props.paused) { | |||
this.focusTrap.pause(); | |||
} | |||
|
|||
// if there is a _FocusTrapReact context from a parent focus trap, pause it | |||
const {_FocusTrapReact: {pause} = {}} = this.context; |
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'd prefer some more straightforward non-destructured code here — something like if (this.context.FocusTrapParent && this.context.FocusTrapParent.pause) { .. }
— especially because this instance has its own pause
function.
|
||
// if there is a _FocusTrapReact context from a parent focus trap, pause it | ||
const {_FocusTrapReact: {pause} = {}} = this.context; | ||
if (pause) pause(); |
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.
If I recall right, focus-trap itself should already pause any active focus traps when a new once is activated: https://github.com/davidtheclark/focus-trap/blob/6ecf7575401b2050cf2f3935544b82d75155c560/index.js#L101-L105. Does that work in this context? If so, maybe we could remove this part of the change?
Then the only trick would be unpausing the previously active trap — similar to this focus-trap demo: https://github.com/davidtheclark/focus-trap/blob/6ecf7575401b2050cf2f3935544b82d75155c560/demo/js/nested.js
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.
That actually makes me wonder if this is how all focus-traps should behave, whether in React or not. If the activation of trap B ends up pausing trap A, should the deactivation of trap B always end up unpausing trap B?
I think that does make a lot of sense. It would need to keep a queue of pauses, for example: trap 1 activates couple things could happen
which I think opens an additional question: if a trap is paused by another trap, does de-activating and re-activating the first trap make it the active trap? |
These are all good points, @chandlerprall, and make me second guess whether we should tray to automate this at all — given the complexity, and the danger of edge cases. Within React I guess we have the advantage that when you nest trap 2 in trap 1, you cannot unmount/deactivate trap 1 without also deactivating trap 2. I wonder, then, if we should just move forward with auto-unpausing in the React context and see how it goes? |
Ha! Based on
My original use case should have worked. Turns out
IMO, a consumer managing the active state of multiple, competing traps has a bad UX flow and should more or less result in undefined behaviour. The only valid use case I can think of is a modal taking focus from something else, but even then the original trap shouldn't pause & re-activate, that would be a terrible flow for a user. So I feel the above queue management approach is fine for all situations. |
@chandlerprall: (I've been on vacation the past week ...) The last-in-first-out queue idea sounds like a good one. I agree with you that nesting of focus traps is not desirable except in some pretty rare situations. So I wonder if we could add it to focus-trap as a new opt-in option, |
I hope you had a good vacation! I feel this shouldn't be behind a flag; I believe the current implementation around |
Ok, @chandlerprall, I think I'd be ok with another major release of focus-trap that introduces this change. However, I do want a flag to turn it off (it could be on by default), in case someone's special situation means they don't want that behavior. |
Sounds good, I'll take a shot at that. |
With 4b6e412, released in 6.0.0, the trap-queueing built into focus-trap is now available in this library. |
Closes #27
This change adds a
_FocusTrapReact
object to the React context when a focus trap is used. Subsequent child focus traps look for this object and, if it exists, uses it to pause the parent trap on child mount and unpause the parent when the child unmounts.I'm not completely sure about how the props should interact with the pausing/unpausing (lines 32-37), I decided to always call
pause
and only callunpause
if pause=falseI tested this change in the setup described in #27 and it works as expected.