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

[Overlay] Enforce focus after backdrop mouse down #1330

Merged
merged 2 commits into from
Jul 28, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions packages/core/src/components/overlay/overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,15 @@ export class Overlay extends React.Component<IOverlayProps, IOverlayState> {
}

private handleBackdropMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
if (this.props.canOutsideClickClose) {
safeInvoke(this.props.onClose, e);
const { backdropProps, canOutsideClickClose, enforceFocus, onClose } = this.props;
if (canOutsideClickClose) {
safeInvoke(onClose, e);
}
if (enforceFocus) {
Copy link
Contributor

Choose a reason for hiding this comment

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

would be super great to add a unit test for this case. we have others for enforceFocus, should be a matter of copy-and-edit.

Copy link
Contributor Author

@llorca llorca Jul 10, 2017

Choose a reason for hiding this comment

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

Failing so far. What I have:

it("returns focus to overlay after clicking the backdrop if enforceFocus=true", () => {
    wrapper = mount(
        <Overlay enforceFocus={true} canOutsideClickClose={false} inline={true} isOpen={true}>
            {createOverlayContents()}
        </Overlay>,
    );
    wrapper.find(BACKDROP_SELECTOR).simulate("mousedown");
    assert.equal(document.querySelector(BACKDROP_SELECTOR), document.activeElement);
});

I can't figure out what the last line should be. As of now, it gives me this:

expected null to equal <body class="">

which I don't understand because the BACKDROP_SELECTOR element exists, also why is document.activeElement equals to body after mousing down on the backdrop?

Copy link
Contributor

Choose a reason for hiding this comment

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

✅ test fixed

// make sure document.activeElement is updated before bringing the focus back
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure I understand this comment -- what's the "before" part about? isn't it updating document.activeElement by bringing the focus back?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Kind of the same problem than in Select and MultiSelect: https://github.com/palantir/blueprint/pull/1275/files#diff-61619f2144645e953195b1eadae9ceadR199

In both this case and the one from the URL above, document.activeElement isn't the expected value after clicking away. From what I understand, rAF allows to wait for the next frame at which point activeElement is the correct value.

Copy link
Contributor

Choose a reason for hiding this comment

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

rAF is decidedly not "the next frame," but the last possible moment of the same frame: before the next repaint. it lets us execute some logic after DOM updates but before repainting (to avoid flicker).

requestAnimationFrame(() => this.bringFocusInsideOverlay());
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 need requestAnimationFrame?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

@adidahiya adidahiya Jul 26, 2017

Choose a reason for hiding this comment

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

that link demonstrates setTimeout, which potentially makes more sense here (as noted in the other comment thread, rAF is not the next frame).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're already using rAF everywhere else we need the updated document.activeElement after triggering an event. Happy to change across the board to maintain consistency, I'll let you and @giladgray decide which is best and I'll open a separate PR if necessary

Copy link
Contributor

Choose a reason for hiding this comment

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

we've enjoyed success using rAF for focus things, more than with setTimeout because it executes later in the same frame. i support this.

}
safeInvoke(this.props.backdropProps.onMouseDown, e);
safeInvoke(backdropProps.onMouseDown, e);
}

private handleDocumentClick = (e: MouseEvent) => {
Expand Down