Change:
container = null; }); <del> it('should support onPress', () => { let buttonRef = React.createRef(); let events = []; function handleOnPress1() { events.push('press 1'); } function handleOnPress2() { events.push('press 2'); } function handleOnMouseDown() { events.push('mousedown'); } function handleKeyDown() { events.push('keydown'); } function Component() { return ( <Press onPress={handleOnPress1}> <Press onPress={handleOnPress2}> <button ref={buttonRef} onMouseDown={handleOnMouseDown} onKeyDown={handleKeyDown}> Press me! </button> </Press> </del> <ins> describe('onPressStart', () => { let onPressStart, ref; beforeEach(() => { onPressStart = jest.fn(); ref = React.createRef(); const element = ( <Press onPressStart={onPressStart}> <div ref={ref} /> </ins> </Press> ); <del> } </del> <ins> ReactDOM.render(element, container); }); </ins> <del> ReactDOM.render(<Component />, container); </del> <ins> it('is called after "pointerdown" event', () => { ref.current.dispatchEvent(createPointerEvent('pointerdown')); expect(onPressStart).toHaveBeenCalledTimes(1); }); </ins> <del> const mouseDownEvent = document.createEvent('Event'); mouseDownEvent.initEvent('mousedown', true, true); buttonRef.current.dispatchEvent(mouseDownEvent); </del> <ins> it('ignores emulated "mousedown" event', () => { ref.current.dispatchEvent(createPointerEvent('pointerdown')); ref.current.dispatchEvent(createPointerEvent('mousedown')); expect(onPressStart).toHaveBeenCalledTimes(1); }); </ins> <del> const mouseUpEvent = document.createEvent('Event'); mouseUpEvent.initEvent('mouseup', true, true); buttonRef.current.dispatchEvent(mouseUpEvent); </del> <ins> // No PointerEvent fallbacks it('is called after "mousedown" event', () => { ref.current.dispatchEvent(createPointerEvent('mousedown')); expect(onPressStart).toHaveBeenCalledTimes(1); }); it('is called after "touchstart" event', () => { ref.current.dispatchEvent(createPointerEvent('touchstart')); expect(onPressStart).toHaveBeenCalledTimes(1); }); // TODO: complete delayPressStart tests // describe('delayPressStart', () => {}); }); </ins> <del> expect(events).toEqual(['mousedown', 'press 2', 'press 1']); </del> <ins> describe('onPressEnd', () => { let onPressEnd, ref; beforeEach(() => { onPressEnd = jest.fn(); ref = React.createRef(); const element = ( <Press onPressEnd={onPressEnd}> <div ref={ref} /> </Press> ); ReactDOM.render(element, container); }); it('is called after "pointerup" event', () => { ref.current.dispatchEvent(createPointerEvent('pointerdown')); ref.current.dispatchEvent(createPointerEvent('pointerup')); expect(onPressEnd).toHaveBeenCalledTimes(1); }); </ins> <del> events = []; const keyDownEvent = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true, </del> <ins> it('ignores emulated "mouseup" event', () => { ref.current.dispatchEvent(createPointerEvent('touchstart')); ref.current.dispatchEvent(createPointerEvent('touchend')); ref.current.dispatchEvent(createPointerEvent('mouseup')); expect(onPressEnd).toHaveBeenCalledTimes(1); }); // No PointerEvent fallbacks it('is called after "mouseup" event', () => { ref.current.dispatchEvent(createPointerEvent('mousedown')); ref.current.dispatchEvent(createPointerEvent('mouseup')); expect(onPressEnd).toHaveBeenCalledTimes(1); }); it('is called after "touchend" event', () => { ref.current.dispatchEvent(createPointerEvent('touchstart')); ref.current.dispatchEvent(createPointerEvent('touchend')); expect(onPressEnd).toHaveBeenCalledTimes(1); }); // TODO: complete delayPressStart tests // describe('delayPressStart', () => {}); }); describe('onPressChange', () => { let onPressChange, ref; beforeEach(() => { onPressChange = jest.fn(); ref = React.createRef(); const element = ( <Press onPressChange={onPressChange}> <div ref={ref} /> </Press> ); ReactDOM.render(element, container); }); it('is called after "pointerdown" and "pointerup" events', () => { ref.current.dispatchEvent(createPointerEvent('pointerdown')); expect(onPressChange).toHaveBeenCalledTimes(1); expect(onPressChange).toHaveBeenCalledWith(true); ref.current.dispatchEvent(createPointerEvent('pointerup')); expect(onPressChange).toHaveBeenCalledTimes(2); expect(onPressChange).toHaveBeenCalledWith(false); }); }); describe('onPress', () => { let onPress, ref; beforeEach(() => { onPress = jest.fn(); ref = React.createRef(); const element = ( <Press onPress={onPress}> <div ref={ref} /> </Press> ); ReactDOM.render(element, container); </ins> }); <del> buttonRef.current.dispatchEvent(keyDownEvent); // press 1 should not occur as press 2 will preventDefault expect(events).toEqual(['keydown', 'press 2']); </del> <ins> it('is called after "pointerup" event', () => { ref.current.dispatchEvent(createPointerEvent('pointerdown')); ref.current.dispatchEvent(createPointerEvent('pointerup')); expect(onPress).toHaveBeenCalledTimes(1); }); </ins> }); <del> it('should support onPressStart and onPressEnd', () => { let divRef = React.createRef(); let events = []; </del> <ins> describe('onLongPress', () => { let onLongPress, ref; beforeEach(() => { onLongPress = jest.fn(); ref = React.createRef(); const element = ( <Press onLongPress={onLongPress}> <div ref={ref} /> </Press> ); ReactDOM.render(element, container); }); it('is called if press lasts default delay', () => { ref.current.dispatchEvent(createPointerEvent('pointerdown')); jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1); expect(onLongPress).not.toBeCalled(); jest.advanceTimersByTime(1); expect(onLongPress).toHaveBeenCalledTimes(1); }); </ins> <del> function handleOnPressStart() { events.push('onPressStart'); } </del> <ins> it('is not called if press is released before delay', () => { ref.current.dispatchEvent(createPointerEvent('pointerdown')); jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1); ref.current.dispatchEvent(createPointerEvent('pointerup')); jest.advanceTimersByTime(1); expect(onLongPress).not.toBeCalled(); }); </ins> <del> function handleOnPressEnd() { events.push('onPressEnd'); } </del> <ins> describe('delayLongPress', () => { it('can be configured', () => { const element = ( <Press delayLongPress={2000} onLongPress={onLongPress}> <div ref={ref} /> </Press> ); ReactDOM.render(element, container); ref.current.dispatchEvent(createPointerEvent('pointerdown')); jest.advanceTimersByTime(1999); expect(onLongPress).not.toBeCalled(); jest.advanceTimersByTime(1); expect(onLongPress).toHaveBeenCalledTimes(1); }); it('uses 10ms minimum delay length', () => { const element = ( <Press delayLongPress={0} onLongPress={onLongPress}> <div ref={ref} /> </Press> ); ReactDOM.render(element, container); ref.current.dispatchEvent(createPointerEvent('pointerdown')); jest.advanceTimersByTime(9); expect(onLongPress).not.toBeCalled(); jest.advanceTimersByTime(1); expect(onLongPress).toHaveBeenCalledTimes(1); }); /* it('compounds with "delayPressStart"', () => { const delayPressStart = 100; const element = ( <Press delayPressStart={delayPressStart} onLongPress={onLongPress}> <div ref={ref} /> </Press> ); ReactDOM.render(element, container); ref.current.dispatchEvent(createPointerEvent('pointerdown')); jest.advanceTimersByTime(delayPressStart + DEFAULT_LONG_PRESS_DELAY - 1); expect(onLongPress).not.toBeCalled(); jest.advanceTimersByTime(1); expect(onLongPress).toHaveBeenCalledTimes(1); }); */ }); }); </ins> <del> function Component() { return ( <Press onPressStart={handleOnPressStart} onPressEnd={handleOnPressEnd}> <div ref={divRef}>Press me!</div> </del> <ins> describe('onLongPressChange', () => { it('is called when long press state changes', () => { const onLongPressChange = jest.fn(); const ref = React.createRef(); const element = ( <Press onLongPressChange={onLongPressChange}> <div ref={ref} /> </ins> </Press> ); <del> } </del> <ins> ReactDOM.render(element, container); </ins> <del> ReactDOM.render(<Component />, container); </del> <ins> ref.current.dispatchEvent(createPointerEvent('pointerdown')); jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY); expect(onLongPressChange).toHaveBeenCalledTimes(1); expect(onLongPressChange).toHaveBeenCalledWith(true); ref.current.dispatchEvent(createPointerEvent('pointerup')); expect(onLongPressChange).toHaveBeenCalledTimes(2); expect(onLongPressChange).toHaveBeenCalledWith(false); }); }); describe('onLongPressShouldCancelPress', () => { it('if true it cancels "onPress"', () => { const onPress = jest.fn(); const onPressChange = jest.fn(); const ref = React.createRef(); const element = ( <Press onLongPress={() => {}} onLongPressShouldCancelPress={() => true} onPressChange={onPressChange} onPress={onPress}> <div ref={ref} /> </Press> ); ReactDOM.render(element, container); // NOTE: onPressChange behavior should not be affected ref.current.dispatchEvent(createPointerEvent('pointerdown')); expect(onPressChange).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY); ref.current.dispatchEvent(createPointerEvent('pointerup')); expect(onPress).not.toBeCalled(); expect(onPressChange).toHaveBeenCalledTimes(2); }); }); // TODO //describe('`onPress*` with movement', () => { //describe('within bounds of hit rect', () => { /** ┌──────────────────┐ *  │  ┌────────────┐  │ *  │  │ VisualRect │  │ *  │  └────────────┘  │ *  │     HitRect    X │ <= Move to X and release *  └──────────────────┘ */ </ins> <del> const pointerEnterEvent = document.createEvent('Event'); pointerEnterEvent.initEvent('pointerdown', true, true); divRef.current.dispatchEvent(pointerEnterEvent); </del> <ins> //it('"onPress*" events are called when no delay', () => {}); //it('"onPress*" events are called after a delay', () => {}); //}); </ins> <del> const pointerLeaveEvent = document.createEvent('Event'); pointerLeaveEvent.initEvent('pointerup', true, true); divRef.current.dispatchEvent(pointerLeaveEvent); </del> <ins> //describe('beyond bounds of hit rect', () => { /** ┌──────────────────┐ *  │  ┌────────────┐  │ *  │  │ VisualRect │  │ *  │  └────────────┘  │ *  │     HitRect      │ *  └──────────────────┘ *                   X   <= Move to X and release */ </ins> <del> expect(events).toEqual(['onPressStart', 'onPressEnd']); </del> <ins> //it('"onPress" only is not called when no delay', () => {}); //it('"onPress*" events are not called after a delay', () => {}); //it('"onPress*" events are called when press is released before measure completes', () => {}); //}); //}); describe('nested responders', () => { it('dispatch events in the correct order', () => { let events = []; const ref = React.createRef(); const createEventHandler = msg => () => { events.push(msg); }; const element = ( <Press onPress={createEventHandler('outer: onPress')} onPressChange={createEventHandler('outer: onPressChange')} onPressStart={createEventHandler('outer: onPressStart')} onPressEnd={createEventHandler('outer: onPressEnd')}> <Press onPress={createEventHandler('inner: onPress')} onPressChange={createEventHandler('inner: onPressChange')} onPressStart={createEventHandler('inner: onPressStart')} onPressEnd={createEventHandler('inner: onPressEnd')}> <div ref={ref} onPointerDown={createEventHandler('pointerdown')} onPointerUp={createEventHandler('pointerup')} onKeyDown={createEventHandler('keydown')} onKeyUp={createEventHandler('keyup')} /> </Press> </Press> ); ReactDOM.render(element, container); ref.current.dispatchEvent(createPointerEvent('pointerdown')); ref.current.dispatchEvent(createPointerEvent('pointerup')); expect(events).toEqual([ 'pointerdown', 'inner: onPressStart', 'inner: onPressChange', 'outer: onPressStart', 'outer: onPressChange', 'pointerup', 'inner: onPressEnd', 'inner: onPressChange', 'inner: onPress', 'outer: onPressEnd', 'outer: onPressChange', 'outer: onPress', ]); events = []; ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'})); // Outer press should not occur as inner press will preventDefault expect(events).toEqual(['keydown', 'inner: onPress']); }); </ins> }); });

Discussion:
This issue tracks tasks related to the React DOM implementation of the experimental React Events API () aka React Flare (our internal code-name for the project). The idea is to extend React's event system to include high-level events that allow for consistent cross-device and cross-platform behavior. Note: For now this is completely experimental and won't affect the open source builds of React. [ ] Document specific UX patterns that this enables and fixes. (inc. from my workplace post on styles, Dan & Brian dev tools, apps, etc.) [x] Every user-facing event should have a . Should we provide a standard way to derive data that might be common across events and require different logic for different positional information, e.g., point, delta, offset, etc? [ ] How to test discrete vs continuous events? [ ] Should we polyfill pointer events or not dispatch emulated touch/mouse events in the core, to avoid multiple event components needing to implement the same logic to ignore emulated events? [x] Investigate native pointer capture for retargeting events - This could be particularly useful for drag/swipe UX. [x] Limit all event components to have a single host node as child? If not enforced for all components, we might need this for Hover/Focus/Press at least. FYI [x] Optimize bundle output. [x] Replace magic object export with [x] Document core API for building responders [x] Stop propagation by default but scope to event module types. [x] Investigate ways to listen to root events without first needing to wait for a target event to be dispatched to the event component. [x] Remove from event object passed to callbacks like [x] Ensure discrete events do not unnecessarily force flush previous non-discrete events. [x] Ensure event responder get unmounted correctly and pending events (e.g. long press) properly get removed when doing so. Maybe an unmount lifecycle method needs adding to event responders? [x] Create a new synthetic event and dispatch mechanism that relies on the event being generated in the event responder module, rather than from React. This allows for the event to be better typed and have a much simpler implementation for how React fires the event when dispatched + it means we can avoid pulling in the current SyntheticEvent system and all its dependencies.[ ] Decide on prop name equivalents to RN and .[ ] Determine the potential use cases for DOM and events.

[x] Cross browser and device/modality testing. [x] add functionality [x] Add README [x] Write unit tests [x] disabled [x] onBlur [x] onFocus [x] onFocusChange [x] onFocusVisibleChange to help attach this info to focus/blur events)[x] Cross browser and device/modality testing. [x] Determine event object data.[x] Write unit tests () [x] disabled [x] onHoverMove [x] onHoverChange [x] onHoverStart [x] onHoverEnd [x] delayHoverStart [x] delayHoverEnd [x] ensure doesn't respond to emulated mouse events [x] Add README [x] Add and props. [x] Rename events to , .[x] Cross browser and device/modality testing. [ ] Behaviour for selecting text within pressable. End press on selection event? Add a new props to configure the behaviour, like ? [x] Allow contextMenu to display when element is a link and ctrl is held down during press [x] Cancel long press if active press moves (exceeding threshold, IIRC RN uses 10px delta) [x] Reactivate when moving out-of-bounds press back into the responder region. [x] BUG: press start -move out of target -release -tap press. The second press doesn't cause to be called, even thought those events are dispatched to the core responder system. () [x] FYI: is needed on to prevent browser cancelling after . [x] Prevent contextMenu appearing during a long press on touch screens. [x] Account for UX involving interactions on links with modifier keys held. [x] Add to control when press is released after moving the pointer away from the press target. [x] Add README [x] Always prevent default on <aclicks. Maybe have an override to turn off behaviour? [x] Rename events to , (). [x] Change to (). [x] Change default to 500ms (see note in ) [x] Add keyboard support for all events. [x] Prevent scroll-down page when pressing Spacebar on keyboard to interact [x] Add and props.

[x] Write unit tests () [x] disabled [x] onLongPress [x] onLongPressChange [x] onLongPressShouldCancelPress [x] onPress [x] onPressChange [x] onPressStart [x] onPressEnd [x] delayLongPress [x] delayPressStart [x] delayPressEnd [x] pressRententionOffset / pressRect [x] emulated mouse events [x] ~fix keyboard press events when metaKey is involved~ (this currently works the same way as native, so will leave it for now) [x] any special case anchor tag-related tests [ ] hitslop interactions [x] Determine event object data. Consider adding and (names tbd) to support userland customisation of focus repair. We could return the native elements. A use case to consider: being able to programmatically move focus to an element without allowing keyboards to focus the element (e.g., the document body, the root of a modal). In this case the element (other than a few special cases) must have . [ ] fires when an input element has been changed. This applies to , , and elements with or when is enabled. provides a callback with the event for the element that changed and a of the elment that changed (if a was supplied). [ ] is similar to , but only provides the value changed. This applies to , , and elements with or when is enabled. provides a and of the element that changed (if a was supplied). [ ] for when any elements trigger form submit. [ ] for when any keyboard keys are pressed. [ ] accepts an array of key strings that will get prevented from entering input. [ ] for when any any text selection occurs in any child elements. [ ] fires before a change is about to occur. This applies to , , and elements with or when is enabled. provides a callback with the event for the element that changed and a of the elment that changed (if a was supplied).[ ] Determine event object data (same as Pan) [ ] Cancelling drag. [ ] FYI: is needed on to prevent browser cancelling after . when combining with . [ ] Add README [ ] Cross browser and device/modality testing. [ ] Write unit tests.[ ] Write unit tests. [ ] Cross browser and device/modality testing.

[ ] Cancelling pan. [ ] Add README [ ] FYI: is needed on to prevent browser cancelling after . [ ] Determine event object data.[x] [x] [ ] [ ] [ ] [ ] [ ] scroll directions [ ] Determine event object data FYI [ ] Combine into w/ event data. [ ] Cancelling swipe. [ ] Write comprehensive unit tests. [ ] Cross browser and device/modality testing. [ ] Add README [ ] Determine event object data (same as Pan) [ ] FYI: is needed on to prevent browser cancelling after . Consider whether we need this at all. Some browsers have a native hitslop and we could work with vendors on any potential improvements to the native system [ ] Figure out how to parent of hitslop. [ ] Measurement without reflows (ResizeObserver?) [ ] Add README [x] Figure out solution for SSR w/ non-touch interactions if no client-side JS [x] Add (). [x] Add SSR support[x] Add fields to event components and event targets. [x] Possibly add a or field to event responders? For example the responder module for ReactDOM could have the name . [ ] Expose some DEV only event triggering exports from event responder modules. i.e. [x] Add basic support for rendering event responders and event targets in the tree. Investigate removing object assign polyfill from individual event modules [x] Add internal interactive documentation / fiddle [ ] Investigate press event patterns on , , , etc. [x] Implement high-level components like Pressable (inc delays). [x] Nested Pressables should not bubble events. [x] Investigate accounting for element resizes when determining hit bounds, e.g., using resize observer for notifications
Added some proposed event object data. Suggest forwarding (or creating) the on all events to allow userland customization based on modality that triggered an event; non-standard type to Focus and Press events.
Tracking some regressions I'm uncovering that seem to have been caused by the changes in . is not called when moving within the responder region. Looks like no events are dispatched to the Press module in the bubble phase, which is also breaking the pressRetention functionality. and events are dispatched to the Hover module in the bubble phase (discovered while investigating the Hover runtime error).

In relation to those two regressions: could you add tests for them that fail, but skip them with a comment explaining they need to be fixed, linking to this issue? You don’t need to put them in Press-test or Hover-test, you can make specific isolated tests with them using a custom responder to demonstrate this breaking because of the linked PR (like how we test the onUnmount works). It seems like a bug in the capture/bubbling rather than a specific event responder module.
I got to the bottom of issue (1) and it turns out it's happening because different event modules can influence the propagation of the same synthetic event. In this case, was preventing the propagation of to the root but was listening to on the root.
We could make root events not get affected by target propagation stopping. We can move the target and the root event handling into separate functions, so that this logic handling root events continues to run. Right now, when a target event handle stopsPropagation, the early return skips over too events too. So we'd move this block of code out:
Yeah I played around with modifying that section of code to do just that. But I think you could still end up in situations where 2 different types of event components are listening to the same event defined as a and the inner component is stopping propagation to the outer, e.g., . That's why I mentioned the idea of limiting control over propagation to within event components of the same type, but haven't thought through the implications enough.
Hi guys. Great work! Will React.unstable_createEventComponent support observers api like ResizeObserver and IntersectionObserver?
Event responder modules are free to use whatever DOM APIs they want, so they could definitely use and . We may even need to use them in some of the core responders soon.
Question regarding potential scroll module - have you considered adding smth like property? It's highly useful to know if the scroll was triggered programatically or by user and it's also highly hard to determine this in user space.
What's an example of scroll being programmatically triggered without your knowledge?
I feel like every change to in code is not user introduced.
But that's easy to detect because you're using a programmatic API
It does get a bit difficult when the scrolling trigger is far from the listener, and making them aware of each other introduces fragile coupling. We've had a few such cases in DevTools.

Especially if the focus signal is reactive to some state change.
Here's what I ask: if you have a request please take the time to provide plenty of context. What are the use cases? How are things difficult? How do you imagine a solution might work? etc.
In example showing a "sticker" with a date at the top of the message feed. When a scroll is induced programmatically (or by the browser) then it's distracting to show such thing, but when a user scrolls on their own it makes sense from the UX perspective. Well, it's not a clear way of determining that in onScroll, so some kind of other tracking flags have to be introduced to solve this. It's in general tricky to handle all possible reasons of why scroll has been triggered: it can be caused by wheel (but also wheel doesnt always mean that scroll will happen - u can have other interactions dependant on the wheel), but also you can wheel over inner scrollable element which won't trigger the scroll of the component in question touchmove - but you probably have to calculate this based on some deltas etc keyboard - space, page up, page down, end, home, arrows And those are just things that user can do. Other than that UI might get mutated (even by React, so we can't really track it in userland) and thus trigger scroll event, and as mentioned setting scrollTop also triggers scroll event. And even if u manage to track all reasons of user-induced scrolling it's unobvious when to clear you your flag(s) because you have to consider inertia scrolling etc The solution would be to figure out best heuristics of detecting this. Can't give much detail right now, because this truly is an overcomplicated problem for such a "simple" thing. Web
I don't quite understand the use case you're describing.
Consider another one - a floating "scroll to bottom" button which covers part of the chat feed. We'd like to show such when a user is scrolling on their own (with a delay etc) and hide it after they stop scrolling so we don't cover part of the chat feed constantly. And we i.e. have to work around for situations like mobile keyboard triggering onScroll (cause of the resize etc).

Given the grand retooling happening to the event system here, I would like to re-raise the following request under this umbrella, from TL;DR: listen to / rather than / native events.
Hey guys, looks pretty cool this experimental event system. I wonder if there is still work to do, I would like to contribute to this. I see there are some pending tasks in the checklist but no open issues about them
We're not longer working on this particular approach and will be exploring other ways to work with the DOM event system in the future. We've concluded that the "Flare" event system is too high-level an abstraction and we'd like to explore something that is a bit more familiar to developers familiar with the DOM (e.g., addEventListener) and React's existing tools (e.g., hooks). Our goal is still to make it possible for library authors to work with passive events, capture/bubble phase, custom events, and events occurring on the document from within a React function reducing the amount of event-related code needed in ReactDOM. Ultimately building both intermediate abstractions like the Responder Event system and high-level abstractions like (which we prototyped in Flare) should be possible in user-space.
That's a bummer - this API looked awesome and I had high hopes for it. I guess you have good reasons for not pursuing this, I'm eager to learn with what alternative you will come up with.