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

[Flare] Add InteractOutside event responder surface #15791

Closed
wants to merge 14 commits into from

Conversation

trueadm
Copy link
Contributor

@trueadm trueadm commented May 31, 2019

When testing Flare internally, we ran into quite a few cases where functionality was needed to detect press events that occured outside of a pressable target region. To tackle this effectively, we deemed a new event responder surface might be ideal. This PR adds the InteractOutside event surface, that detects presses that occur outside of the children within the event responder. Notably these are the current supported features for InteractOutside:

  • onInteractOutside
  • interactOnScroll (defaults to false)
  • interactOnBlur (defaults to true)
  • disabled

Here's an example of how this might work:

function Dialog({
  children,
  closeDialog,
  styles,
  autoFocus,
hasOverlay,
}) {
  // It both works with or without an overlay!
  // No invisible overlay hacks needed!
  return (
    <>
      { hasOverlay && <DialogOverlay styles={styles} /> }
      <InteractOutside onInteractOutside={closeDialog} interactOnBlur={false}>
        <FocusScope contain={true} autoFocus={autoFocus}>
          <DialogContainer onEscapeKey={closeDialog}>
            <DialogHeader onExit={closeDialog} />
            {children}
          </DialogContainer>
        </FocusScope>
      </InteractOutside>
    </>
  );
}

Interaction events that start within the InteractOutside responder are handled so they can't leak to outside the responder. Furthermore, interaction events that originate from outside the responder will not fire onInteractOutside if they move to within the responder after. This implementation should account for portals, something no other implementation seems to properly do – so there's a definite benefit here. InteractOutside can also support many children, so it's not constrained to having a single child.

@sizebot
Copy link

sizebot commented May 31, 2019

Details of bundled changes.

Comparing: 113497c...e1ad849

react-events

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-events-interact-outside.development.js n/a n/a 0 B 7.02 KB 0 B 2.04 KB UMD_DEV
react-events-swipe.development.js 0.0% +0.1% 6.19 KB 6.19 KB 1.76 KB 1.76 KB UMD_DEV
react-events-interact-outside.production.min.js n/a n/a 0 B 2.98 KB 0 B 1.3 KB UMD_PROD
react-events-focus.development.js 0.0% -0.1% 7 KB 7 KB 1.78 KB 1.78 KB UMD_DEV
ReactEventsPress-dev.js 0.0% -0.1% 23.58 KB 23.59 KB 5.45 KB 5.44 KB FB_WWW_DEV
ReactEventsPress-prod.js -0.0% -0.0% 19.74 KB 19.74 KB 4.07 KB 4.07 KB FB_WWW_PROD
react-events-focus.production.min.js 0.0% -0.1% 2.83 KB 2.83 KB 1.08 KB 1.08 KB NODE_PROD
react-events-drag.production.min.js 0.0% -0.1% 2.54 KB 2.54 KB 1.16 KB 1.16 KB UMD_PROD
react-events-drag.development.js 0.0% -0.0% 7.5 KB 7.5 KB 2.3 KB 2.3 KB NODE_DEV
react-events-interact-outside.development.js n/a n/a 0 B 6.83 KB 0 B 1.99 KB NODE_DEV
react-events-interact-outside.production.min.js n/a n/a 0 B 2.79 KB 0 B 1.24 KB NODE_PROD
react-events-press.development.js 0.0% -0.1% 22.77 KB 22.78 KB 5.33 KB 5.33 KB UMD_DEV
react-events-focus-scope.development.js 0.0% -0.1% 4.79 KB 4.79 KB 1.45 KB 1.45 KB UMD_DEV
react-events-press.production.min.js -0.1% -0.2% 8.16 KB 8.15 KB 2.89 KB 2.88 KB UMD_PROD
ReactEventsInteractOutside-dev.js n/a n/a 0 B 7.05 KB 0 B 2.04 KB FB_WWW_DEV
ReactEventsInteractOutside-prod.js n/a n/a 0 B 5.84 KB 0 B 1.59 KB FB_WWW_PROD
react-events-press.development.js 0.0% -0.1% 22.59 KB 22.59 KB 5.27 KB 5.27 KB NODE_DEV
react-events-focus-scope.development.js 0.0% -0.1% 4.6 KB 4.6 KB 1.4 KB 1.4 KB NODE_DEV
react-events-press.production.min.js -0.1% -0.1% 7.98 KB 7.97 KB 2.83 KB 2.83 KB NODE_PROD

Generated by 🚫 dangerJS

@TrySound
Copy link
Contributor

This is awesome! Thank you @trueadm

@ryanflorence
Copy link
Contributor

ryanflorence commented May 31, 2019

In my experience, 99% of the time on the web that you think you want "click outside" you actually want "blur".

People navigating with the keyboard "click outside" by tabbing out of something and into something else. Click outside is, by definition, only handling mouse users, and a lot of the web is broken for people who can't use a mouse because of this idea of "click outside".

And even for mouse users, there are some browser quirks that make it technically unfeasible as well.

For example, in macOS Safari, every "click outside" implementation I've ever seen doesn't work when you click a <select> because the event can't be captured by a document click handler (it doesn't bubble 🥴).

https://codesandbox.io/s/affectionate-dust-fmkct

Common cases when people think they want outside click:

  • Dialog (like the example here), keyboard tabbing is trapped to the dialog so blur won't work, so you might think you want outside click, but it's a whole lot easier to add a click event to the portal that rendered the dialog whose size fills up the entire viewport.

  • Toggletips (click a [?] or whatever and a popup shows up that may have interactive content). Again, you don't want outerclick because you want keyboard users tabbing out of the popover to close it. You want blur.

  • Menus (all sorts) whenever the menu is open you may think you want outside click but again, you really want blur. In some cases, you disable tab navigation altogether, so blur might not sound right anymore, however, blur will still work perfectly because an outside click causes a blur, no need for a new event. Also, they may "outside click" a <select> and the menu would remain open, unless you use blur.

So anyway, just want to let you know, "there be dragons" ... and you probably want blur.

@Andarist
Copy link
Contributor

Andarist commented May 31, 2019

People navigating with the keyboard "click outside" by tabbing out of something and into something else. Click outside is, by definition, only handling mouse users, and a lot of the web is broken for people who can't use a mouse because of this idea of "click outside".

IMHO that's not an argument against "click outside" per se, but rather in favour of handling different interactions properly. By using "click outside" you don't necessarily have to ignore keyboard interactions - they just have to be handled too.

And even for mouse users, there are some browser quirks that make it technically unfeasible as well.

Could you elaborate? I'm curious.

For example, in macOS Safari, every "click outside" implementation I've ever seen doesn't work when you click a select because the event can't be captured by a document click handler (it doesn't bubble ).

I haven't made extensive tests, but it works on Chrome for me (using your demo).

Dialog (like the example here), keyboard tabbing is trapped to the dialog so blur won't work, so you might think you want outside click, but it's a whole lot easier to add a click event to the portal that rendered the dialog whose size fills up the entire viewport.

Yes, you kinda can do that, but you have to call stopPropagation on inner element to make it really work, which kinda sucks.

@ryanflorence
Copy link
Contributor

ryanflorence commented May 31, 2019

... it works on Chrome for me (using your demo).

Remember this? "For example, in macOS Safari" 🥺

By using "click outside" you don't necessarily have to ignore keyboard interactions - they just have to be handled too.

If you handle keyboard, you've already handled "click outside". Blur is click outside, but only if you've handled it.

Yes, you kinda can do that, but you have to call stopPropagation on inner element to make it really work, which kinda sucks.

Only kinda. But creating a first-class event for an inaccessible pattern completely sucks. (Not to mention it's redundant when you've implemented blur already, which you should have done in the first place).

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

@ryanflorence There are a bunch of things here I guess. This implementation doesn't use click per say, but rather the concept of a press – which may be keyboard, mouse, pen, touch etc. There are so many issues with using the native blur and focus events, which is why with React Flare and this project, we actually emulate and mostly override the control of the browser with an internal synthetic version – just like we do for propagation and bubbling. With React Flare, there is no longer any preventDefault and stopPropagation, those imperative methods never leak to the user-callback events anymore, so we need to ensure that a declarative up-front event component is architected in the first place.

With FocusScope for example, we actually use the keyboard and track tab presses – rather than using the native tab interface for moving around focus. Otherwise we can't properly contain focus, nor can we traverse through portals correctly. Which also leads back to issues when dealing with blur and is why the PressOutside component is far more than just detecting presses outside. It uses the React Fiber tree to traverse everything which makes patterns like this possible:

<Press>
  <FixedFullScreenOverlay />
</Press>
<Portal>
  <Press>
    <Modal />
  </Press>
</Portal>

In the above example, it's very difficult to known what will fire first – the <Press> in the portal, or the full screen overlay? It may depend on the styling of the overlay, but with Flare, the heuristics of ordering shouldn't necessarily depend on ordering when you want to check if something didn't occur within a region? This approach, as you mentioned, is where many engineers stumble and their implementations tend to have edge-cases in that lead to error-prone designs; especially for devices that don't properly handle events on fixed full-screen overlays (like iOS).

If you take the approach in this PR:

<Portal>
  <PressOutside>
    <Modal />
  </PressOutside>
</Portal>

Not only is it easier to understand, under the hood the event responder is doing all the complex work to ensure that focus, keyboard and pointer events propagate and work in accordance to React's component model – not the DOM model (otherwise you'll run into issues with suspense and portals).

If you want to handle the user tabbing out of a modal, dropdown or tooltip you'd use FocusScope which would handle that for you. There's nothing wrong with composing multiple event components together with React Flare. It's actually what we'll be promoting. Of course, in the ideal world we could just use blur and there'd be no edge cases with the React event system or with browser differences, but we're a long way from that day.

@necolas
Copy link
Contributor

necolas commented May 31, 2019

This is how you can handle pointers on the overlay without a new responder:

<Portal>
  <Press onPress={closeModal}>
    <Overlay />
  </Press>
  <Modal>
</Portal>

Demo: https://codesandbox.io/s/experimental-event-api-e1d0i

This is also a pattern that works for any other kind of menu where the underlying document should not be interactive and an overlay is used (e.g., dropdown menus, see mobile.twitter.com)

As Dominic mentioned, there are lots of additional details related to the UX we're researching, including stuff like keyboard navigation as per ARIA spec for menus, etc. So we'll be experimenting with abstractions to support the use cases we have internally and absorbing the FB accessibility patterns that currently rely on hacks using native events outside of React.

@Andarist
Copy link
Contributor

Remember this? " For example, in macOS Safari"

Oh, my bad - no idea how I've missed that. Have to be really tired today 😅

@trueadm one use case which comes often in other implementations is to "ignore" some element, most commonly a "trigger" element, so a click on the trigger element only toggles the other component, but doesnt cause "click outside" handler - otherwise you might end up with closing element because of the "click outside" and you might fire click handler which triggers the element again.

Not sure how to solve it as such trigger element lives outside of the handled tree, so it doesn't fit nicely with the current model which affects only wrapped subtree.

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

@necolas Yeah, that was my original idea I threw around internally on that diff a few days ago but I thought that means that the overlay now needs to block the screen rather than let events pass through? Plus it means having an overlay to begin with, the approach in this PR doesn't require that (I updated the posts above). I guess an invisible overlay isn't too bad as that's how we've always dealt with this problem – but it doesn't mean we have to do it that way. :)

@ryanflorence

For example, in macOS Safari, every "click outside" implementation I've ever seen doesn't work when you click a select because the event can't be captured by a document click handler (it doesn't bubble ).

I just checked and I seem to get a mousedown event fire for pressing a select. We don't actually use click events with React Flare, we use pointer events, falling back to mouse events and we fire press events in the mouseup/pointerup events.

@Andarist that sounds like a state machine problem. If you have something open that uses PressOutside, any triggers that open the the component that mounts PressOutside should either be no-ops or should also unmount the component tree that has the PressOutside in. Like if you press the Windows logo to open the start menu, you can both close the start menu by pressing the Windows logo again, or by pressing outside of the start menu – it's all handled with state machines.

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

I'm also happy to scrap this event responder proposal if the general consensus is that we're happy to approach the problem with a different solution. I'm not so attached, but I do think there's a certain element of validity into how we can utilize the power of the new event API to do things that just weren't possible before with React. Treat this PR as more of a dip into R&D as to what is possible :)

@ryanflorence
Copy link
Contributor

ryanflorence commented May 31, 2019

@trueadm Sorry, "click outside" is just a major trigger for me because it's responsible for a huge amount of inaccessible interactions on the web.

I think I'm really just trying to say:

  • make sure this works when you click on a select
  • maybe the name is wrong, this could wrap up a lot more than just "press outside"

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

I confirm it works with select fine in iOS and desktop Safari. It also plays nicely with portals and other React-isms. Maybe the naming is off, as it's really tracking for an interaction with keyboard/mouse/touch/pen that doesn't track to a node in the fiber tree that is a child of PressOutside. It's far less to do with "click" then it may seem – in fact, it doesn't even listen to click at all!

@yuchi
Copy link

yuchi commented May 31, 2019

@trueadm May I propose InteractOutside? Should cover @ryanflorence criticism (which, btw, I completely agree with)

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

@yuchi Good shout. Renamed :)

@trueadm trueadm changed the title [Flare] Add PressOutside event responder surface [Flare] Add InteractOutside event responder surface May 31, 2019
@ryanflorence
Copy link
Contributor

ryanflorence commented May 31, 2019

I would expect focus of some element outside of the target to trigger this event, too, and that should cover the keyboard users.

In firefox/safari macOS <button> does not take focus when clicked (😡), so you may need to get tricky with "blur" and seeing where focus rests instead (🥵), ofc maybe your implementation of events here will handle it just fine too.

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

@ryanflorence I tried using focus like an hour ago after the discussions in this thread. I can't see it working because focus changes when you click off the browser, when you switch tab or when you search the page. These are cases where we interaction hasn't actually been moved to outside – unless you mean that we need to conditionally exclude these cases too? To be honest, these also seem like cases where FocusScope is best fit, as it also has knowledge of containment and order – so it's likely that you'd use InteractOutside and FocusScope together.

@ryanflorence
Copy link
Contributor

Native <select> closes when the browser window loses focus, seems to be the right model?

@ryanflorence
Copy link
Contributor

these also seem like cases where FocusScope is best fit, as it also has knowledge of containment and order – so it's likely that you'd use InteractOutside and FocusScope together.

I don't really know very much about FocusScope, but "clicking an element" or "tab navigating to it" should result in the same behavior, seems weird to use InteractOutside for one, and then something else for the other, especially when the effort made for "tab navigating" will also, naturally, handle the clicking (whether its based on focus or blur).

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

@ryanflorence I actually highly dislike this and that's why custom implementations don't work this way. The classic one is when you've opened a select and you need to cross-check something. Say you're flying to a country and you want to check you have the right location in the booking form – I go to my email and double check – to only come back and the <select> has closed and now I have to scroll through 300 locations again, when I was on the right one the first time around?

Just because browsers do something one way, it doesn't mean it's best in all cases. InteractOutside isn't meant to be the one true solution for all cases.

I don't really know very much about FocusScope, but "clicking an element" or "tab navigating to it" should result in the same behavior, seems weird to use InteractOutside for one, and then something else for the other, especially when the effort made for "tab navigating" will also, naturally, handle the clicking (whether its based on focus or blur).

We can handle focus for when it's not the window or document – where we can be exclusions. This is what I asked about above. I have no issue in handling loss of focus as an interaction.

@ryanflorence
Copy link
Contributor

and that's why custom implementations don't work this way.

I think that's inaccurate. They work this way because people haven't researched this stuff or used a keyboard on their own UI. There may be some edge case benefits to a naively implemented UI control, and you've named one of them.

@ryanflorence
Copy link
Contributor

because browsers behave differently.

Again, this isn't about browsers. Your native OS UIs work this way too.

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

I think that's inaccurate. They work this way because people haven't researched this stuff or used a keyboard on their own UI. There may be some edge case benefits to a naively implemented UI control, and you've named one of them.

Again, this isn't about browsers. Your native OS UIs work this way too.

I've not suggested anything that differs from native OS UIs. Rather than get into a semantics argument with you, I'll instead change the direction of this thread back to the implementation of this PR.

Correct me if I'm wrong, but you suggested that InteractOutside would fire onInteractOutside if focus fired on a node that wasn't a child of the <InteractOutside>. So if focus fired on the window when we change browser tabs, then it should fire? That's where we disagree.

@ryanflorence
Copy link
Contributor

So if focus fired on the window when we change browser tabs, then it should fire? That's where we disagree.

When a browser window--or a native OS app window--loses focus, an open dropdown will close. That's what I'd expect with InteractOutside.

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

@ryanflorence That wouldn't work for dialog/modals though right? Are you saying you should be able to configure InteractOutside with a role so that it knows which is best to use?

@gaearon
Copy link
Collaborator

gaearon commented May 31, 2019

I just wanted to add none of these APIs are set in stone or even affect open source yet. We’re just experimenting with what might work or not, and learning from that. There will be opportunities to discuss the details of the API after we know it’s something we actually think is shippable. That may, and likely will, take months.

@ryanflorence
Copy link
Contributor

ryanflorence commented May 31, 2019

A modal dialog should stick around, yeah, just like a native OS modal dialog does.

I guess I'm concerned an event like this will encourage people to just slap it on a bunch of components when the interactions are all more nuanced.

I'll get out of here because I think I'm just distracting the conversation, I'm thinking about specific cases that I see patterns like this applied to and you're just after a low-level thing that may or may not apply to dialog, a menu, or whatever else.

Interacting outside of an element seems like an interesting event, how people will apply (and have applied it) in the past bothers me.

I do believe that click and focus of an element outside should trigger this. If you need to special case "window" then that's a different discussion.

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

@ryanflorence Event components in this new API aren't meant to be consumed at a high-level, by design. They are very powerful, so it gives people building rich shared components for a UI the power to do certain things that wouldn't normally be possible. Someone using these would never use them directly. They'd use MyCompanyModal or MyCompanyDropdown which would, behind the scenes, use like 5+ event components – Press, FocusScope, InteractOutside, Hover and Focus.

I'll add a new prop interactOnBlur that you can pass to InteractOutside that will also fire onInteractOutside for blue events. :)

@ryanflorence
Copy link
Contributor

Sounds good, please default to true if your goal is to get rid of me on this thread 😘

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

@ryanflorence Yep, I've made it default to true. Thank you for the feedback – sorry if you felt frustrated by this, it's a complex topic and I'm glad we got to where we have now.

@theKashey
Copy link
Contributor

Uncovered cases, which could be covered if the current implementation becomes a bit more generic.

  • on-click-outside, but not in these nodes. Not all interactions "outside" should toggle the trigger.
  • on-outside, but not click. For example scroll.
  • sometimes you have not to trigger an event on event, but cancel the original event.

This implementation should account for portals, something no other implementation seems to properly do – so there's a definite benefit here.

Portals case could be handled with native DOM API with almost the same amount of code - store React event on capture phase and compare it with a real event on when it bubbles back - if targets not match - it was outside. But, from some point of view, it could be already a bit late - target element even listener was fired, and event just bubbling back from it.

@trueadm
Copy link
Contributor Author

trueadm commented May 31, 2019

@theKashey

on-click-outside, but not in these nodes. Not all interactions "outside" should toggle the trigger.

How do you envisage an API for that?

on-outside, but not click. For example scroll.

I've added blur which seems to trigger when native elements also blur on scroll. I can add a discrete event here too, but I'd like to know of the use-cases first. We also never use click anywhere in this implementation.

sometimes you have not to trigger an event on event, but cancel the original event.

Can you explain more about this?

Portals case could be handled with native DOM API with almost the same amount of code - store React event on capture phase and compare it with a real event on when it bubbles back - if targets not match - it was outside. But, from some point of view, it could be already a bit late - target element even listener was fired, and event just bubbling back from it.

We've got a bunch of internal implementations that have tried to do this with capture phase events. The issue is that all Flare events are capture phase events with this new event API, so that means that Flare events fire first in almost all cases. There have been hack ways to get around it but they mostly have edge-cases that you'd never think of when it comes to portals – this implementation works well and after the latest iteration of changes, isn't that much code.

Please do note: this work is experimental and focused on internal research first – where we can flesh out known issues.

@theKashey
Copy link
Contributor

theKashey commented Jun 1, 2019

How do you envisage an API for that?

Two ways:

  • wrap another node with enabled InteractOutsideIgnore. Ie "white list".
  • provide a list of ref to InteractOutside to the elements, which shall be considered internal.

I can add a discrete event here too, but I'd like to know of the use-cases first / Can you explain more about this?

  • scroll lock and other implementations of inert. The same FocusScope but for all events.

that means that Flare events fire first in almost all cases

:Nods:, that's why I said "it could be already a bit late", and using personally to prevent browser behaviour only (ie .preventDefault), not the event itself.


Another strange (my) use case:

  • there is a menu
  • click on the every menu item opens a drop down
  • click outside closes dropdown
  • focus is scoped to dropdown + menu

Problems:

  • two different FocusScope are active simultaneously - one for a portaled Dropdown, and one for a menu
  • if you close dropdown on click-outside - you will close the current one.
  • if you click on a menu item - you will open a new one
  • (obviously) if you click the same menu item - it will 1. close the current one, 2. instantly open it back. Which violates rules above.

That leads to the question - would be initial event handler or the "outer" element be called:

  • if not, then clicking another menu item will not open a new dropdown - you will have to click twice.
  • if yes, then before or after the onInteractOutside handlers?
  • if after - then problem above will be resolved, but another ones might emerge - you never know what customer might want.

So - that should be customisable. The same "capture"/"bubble" phases with ability to "stopPropagation" for any event, but outside


What I want:

<Outsider
  onClick={onClickOutside}
  onKeyDownCapture={onKeyOutsideCapture}
>
  <div
     onClick={onClickInside}
    onKeyDownCapture={onKeyInsideCapture}
  />
/>

@trueadm
Copy link
Contributor Author

trueadm commented Jun 1, 2019

@theKashey Given your use-case and looking at internal cases of internal scrollers, I agree there should be some non-default option to interact with scroll. So I've added interactOnScroll prop, which default to false along with tests.

I still don't get why you want to exclude interactions from other nodes. It seems like a code-smell. If you have another interactive element on the page that conflicts with the state machine for why the overlay/dropdown/tooltip is showing, then you should resolve that using internal state for that component rather than having to wrap them around < InteractOutsideIgnore>. We've had to do something similar internally, we had to change the state machine. It takes some getting used to, but with hooks and context, this is far more comprehensible than coupling the event responders with component state – which is what you're essentially doing here.

Your other use case seems centered around nested InteractOutside event components. That should be dealt with by the fact the event responders for InteractOutside use stopLocalPropagation set to true which means they happen in bubble order. That's a pretty unique feature of this new event system, but it should work. I'll add some test for it next week that confirm this behaviour matches your use-case outlined.

That leads to the question - would be initial event handler we called,

Yes, you don't need to click twice. We don't stop the propagation to other events, both using the new event system with <Press> or <div onClick> styles.

@theKashey
Copy link
Contributor

I still don't get why you want to exclude interactions from other nodes. It seems like a code-smell.

You missed the case of joining two FocusScopes in a "group" - to be able to tab from one group to another, which also means what two InteractOutside should be a able to form a group.
This is not about exclusion, but inclusion - there are situations when you have to inert a whole page, except the thing you are focusing customer on, and a menu, for example.

const Header = ({taskRef}) => (
      <InteractOutside controlledBy={taskRef}>
        <FocusScope controlledBy={taskRef}>
            {children}
        </FocusScope>
      </InteractOutside>
);

const Modal = (taskRef) => (
     <InteractOutside ref={taskRef} onInteractOutside={closeDialog} interactOnBlur={false}>
        <FocusScope contain={true} autoFocus={autoFocus} controlledBy={taskRef}>
          <DialogContainer onEscapeKey={closeDialog}>
            <DialogHeader onExit={closeDialog} />
            {children}
          </DialogContainer>
        </FocusScope>
      </InteractOutside>
)
// ok, InteractOutside shall be combined into one "god controller" for this case.

@trueadm
Copy link
Contributor Author

trueadm commented Jun 1, 2019

As long as you nest event responders, their effects should be grouped by default without having refs. You just ensure that you provide the close event on the upper most InteractOutside event component. They’re grouped by being children/siblings of other FocusScope/InteractOutsides event components.

@steveluscher
Copy link
Contributor

This is how you can handle pointers on the overlay without a new responder:

<Portal>
  <Press onPress={closeModal}>
    <Overlay />
  </Press>
  <Modal>
</Portal>

Demo: https://codesandbox.io/s/experimental-event-api-e1d0i

@necolas: I was able to break this example by mousing down outside the pressable, dragging the pointer over top of it, then releasing the mouse button. I would expect that not to trigger the ‘close modal’ log.

@necolas
Copy link
Contributor

necolas commented Jun 3, 2019

That's probably a bug in Press

@devongovett
Copy link
Contributor

I'm a big fan of this idea. I've come across a bunch of use cases where this would be really useful, and it's currently difficult to do this correctly in user space due to React specific concepts like portals. 👍

@necolas
Copy link
Contributor

necolas commented Dec 17, 2019

Closing as we're no longer developing this API. See #15257 (comment)

@necolas necolas closed this Dec 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet