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: Events bubbling up to the page can conflict with global shortcuts #1802

Closed
siddharthkp opened this issue Jan 17, 2022 · 8 comments · Fixed by #1824
Closed

Overlay: Events bubbling up to the page can conflict with global shortcuts #1802

siddharthkp opened this issue Jan 17, 2022 · 8 comments · Fixed by #1824
Labels

Comments

@siddharthkp
Copy link
Contributor

siddharthkp commented Jan 17, 2022

via @dusave on slack: Is there a way to stop propagation of Escape when an anchored overlay is open? I just want the anchored overlay to close, but it's also bubbling up outside of the anchored overlay

Outdated repo (This feature was removed in memex, watch this video instead)

https://github.com/orgs/github/projects/4205/settings/fields/55540

  1. Click on the dates of a given iteration, make the date picker show
  2. Hit Escape
  3. Notice it closes the date picker and settings. We only want it to close the date picker

Update:

Problem:

By default, pressing Escape in an Overlay bubbles up to the page outside the Overlay. Adding event.stopPropagation inside onEscape does not stop the events from bubbling up because of the way the handlers are wired.

const Page = () => {
  React.useEffect(() => {
    // global keydown listener on page.
    document.addEventListener('keydown', console.log('global handler:', event.key))
  }, [])

  return (
    <Overlay
      onEscape={event => {
        closeOverlay()
        // You'd expect this to prevent the event from bubbling 
        // to the global handler on the page, but it doesn't
        event.stopPropogation() 
      }}
    >
      ...
    </Overlay>
  )

Scope

This issue of course isn't just limited to "Escape". If an application has global shortcuts, events bubbling out of Overlay can fire global shortcuts.

Why does this happen?

When escape is pressed, you would expect it to be before because Overlay is a child of the Page, but this isn't the case. Overlay uses the useOnEscapePress hook which attaches an event handler on the document, this is great because no matter where the focus is, pressing Escape will close the top most Overlay.

However, because this event listener is on the document, we cannot predict the order in which it will be fired - will it be before the global handler on the page or after it? And that's why stopPropagation and preventDefault will not stop the global handler on the Page from catching this event. Here is a loom with a demo of this

There is a repro of this issue in our storybook setup and a failing test in our test suite that would help in finding a fix for this.

Prior work / failed attempts:

We made an attempt to fix this issue in #1824 by moving the event listener to the Overlay instead of putting it on the document (with container.addEventListener) and adding a event.stopPropagation automatically.

This stopped events from bubbling up, but created a few other bugs:

  1. The Overlay eagerly caught keydown events that were attached to children with onKeyDown inside the Overlay before they fired on the children first. (Story for regression testing)
  2. The Overlay does not catch Escape presses when it is not in focus (which probably a bug anyway?)

Potential fix

After the changes in #1861, we can attempt to move the event listener back to the Overlay container.

  1. We will have to pair it with adding focus trap on the Overlay, so that focus does not move outside the Overlay and all Escape presses are caught by the Overlay.

  2. Lastly, you should be able to call event.stopPropagation inside a keydown event on a child component inside Overlay (like a TextInput) to prevent the Overlay from closing if Escape is pressed. (Story for testing). If the Overlay is eagerly catching events meant for its children, we can move the event listener to a onKeyDown instead of container.addEventListener. Useful reference to event delegation in React

@siddharthkp siddharthkp added bug Something isn't working react labels Jan 17, 2022
@rezrah
Copy link
Contributor

rezrah commented Jan 17, 2022

Initial investigation: Had some difficulty with narrowing down an actual fix. Simply stopping propagation on the escape handler in AnchoredOverlay doesn’t work, as the escape handler doesn’t get called / is short-circuited in an ancestor event handler.

Things tried but didn’t work:

  • Manually intercepting the keyDown handler inside AnchoredOverlay and DatePickerOverlay to stop propagation
  • Short-circuiting the render function on keyboard events
  • Tweaking the capture / focus target for the portal, to prevent it bubbling the synthetic events to ancestor keyboard handlers

May need some more context on how events are handled in the target project before triaging further.

Some related issues I stumbled upon, that indicate similar challenges (and some solutions) with event bubbling in Portals:

facebook/react#11387
facebook/react#19637

@siddharthkp siddharthkp changed the title AnchoredOverlay: stop propagation of Escape Overlay: stop propagation of Escape Jan 17, 2022
@siddharthkp
Copy link
Contributor Author

This is a bug with Overlay abstraction (not AnchoredOverlay), Added repro with our storybook to the description ⬆️

@siddharthkp
Copy link
Contributor Author

Hi! After trying a bunch of things out in primer and application land, finally settled with this: #1824. Escape events on Overlays now simply don't bubble up the tree, which would fix the reported issue and avoid any future ones.

@rezrah
Copy link
Contributor

rezrah commented Jan 27, 2022

Thanks @siddharthkp - just a heads up for anyone waiting on this fix - it will ship as part of #1825

@siddharthkp
Copy link
Contributor Author

This issue is back because after reverting #1824.

Updated the original description with more information.

@siddharthkp siddharthkp reopened this Feb 17, 2022
@siddharthkp siddharthkp changed the title Overlay: stop propagation of Escape Overlay: stop event bubbling to the Page Feb 17, 2022
@siddharthkp siddharthkp changed the title Overlay: stop event bubbling to the Page Overlay: Events bubble up to the page Feb 17, 2022
@siddharthkp siddharthkp changed the title Overlay: Events bubble up to the page Overlay: Events bubble up to the page can conflict with global shortcuts Feb 17, 2022
@siddharthkp siddharthkp changed the title Overlay: Events bubble up to the page can conflict with global shortcuts Overlay: Events bubbling up to the page can conflict with global shortcuts Feb 17, 2022
@siddharthkp siddharthkp removed their assignment Feb 17, 2022
@github-actions
Copy link
Contributor

Hi! This issue has been marked as stale because it has been open with no activity for 180 days. You can comment on the issue or remove the stale label to keep it open. If you do nothing, this issue will be closed in 7 days.

@siddharthkp
Copy link
Contributor Author

This is still a limitation of the Overlay, keeping it open

@siddharthkp siddharthkp reopened this Aug 24, 2022
@siddharthkp siddharthkp removed the Stale label Aug 24, 2022
@lesliecdubs
Copy link
Member

No one is actively asking for this right now so we're going to close for the moment. Let's reopen if needed.

@lesliecdubs lesliecdubs closed this as not planned Won't fix, can't repro, duplicate, stale Aug 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants