Description
This issue tracks tasks related to the React DOM implementation of the experimental React Events API (react-events
) 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.
Core
- Document specific UX patterns that this enables and fixes. (inc. from my workplace post on styles, Dan & Brian dev tools, apps, etc.)
- Every user-facing event should have a
timeStamp
. 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?
- Investigate native pointer capture for retargeting events - https://www.w3.org/TR/pointerevents/#pointer-capture. This could be particularly useful for drag/swipe UX.
- 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 Always use event capturing. Remove media events. #12919
- Optimize bundle output.
- Replace magic object export with
React.unstable_createEventComponent(type: Symbol | number, responder: ReactEventResponder, displayName: ?string)
- Document core API for building responders
- Stop propagation by default but scope to event module types.
- Investigate ways to listen to root events without first needing to wait for a target event to be dispatched to the event component.
- Remove
listener
from event object passed to callbacks likeonPress
- Ensure discrete events do not unnecessarily force flush previous non-discrete events.
- 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?
- 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.
Ownership
- Decide on prop name equivalents to RN
onStartShouldSetResponder
andonMoveShouldSetResponder
.
Focus module
- Determine the potential use cases for DOM
focusin
andfocusout
events. - Cross browser and device/modality testing.
- add
focusVisible
functionality - Add README
- Write unit tests
- disabled
- onBlur
- onFocus
- onFocusChange
- onFocusVisibleChange
- Determine event object data (might require a global "modality" tracker to help attach this info to focus/blur events)
{
pointerType: 'mouse' | 'pen' | 'touch' | 'keyboard'
}
Hover module
- Cross browser and device/modality testing.
- Determine event object data.
{
pointerType: 'mouse' | 'pen',
point: { x: number, y: number }
}
- Write unit tests (Rename hover props in experimental event API and write unit tests #15283)
- disabled
- onHoverMove
- onHoverChange
- onHoverStart
- onHoverEnd
- delayHoverStart
- delayHoverEnd
- ensure doesn't respond to emulated mouse events
- Add README
- Add
delayHoverStart
anddelayHoverEnd
props. - Rename events to
onHoverStart
,onHoverEnd
.
Press module
- 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
onMoveShouldEndPress
? - Allow contextMenu to display when element is a link and ctrl is held down during press
- Cancel long press if active press moves (exceeding threshold, IIRC RN uses 10px delta)
- Reactivate when moving out-of-bounds press back into the responder region.
- BUG: press start -> move out of target -> release -> tap press. The second press doesn't cause
onPressStart/Change
to be called, even thought those events are dispatched to the core responder system. (Event API: Fix bug where Press root events were not being cleared #15507) - FYI:
touchAction:'none'
is needed oncurrentTarget
to prevent browser cancelling afterpointermove
. - Prevent contextMenu appearing during a long press on touch screens.
- Account for UX involving interactions on links with modifier keys held.
- Add
pressRetentionOffset
to control when press is released after moving the pointer away from the press target. - Add README
- Always prevent default on clicks. Maybe have an override to turn off behaviour?
- Rename events to
onPressStart
,onPressEnd
(Rename press props in experimental event API #15263). - Change
longPressCancelsPress
toonLongPressShouldCancelPress
(Rename press props in experimental event API #15263). - Change default
delayLongPress
to 500ms (see note in Add tests for Press responder event module #15290) - Add keyboard support for all events.
- Prevent scroll-down page when pressing Spacebar on keyboard to interact
- Add
delayPressStart
anddelayPressEnd
props. - Write unit tests (Add tests for Press responder event module #15290)
- disabled
- onLongPress
- onLongPressChange
- onLongPressShouldCancelPress
- onPress
- onPressChange
- onPressStart
- onPressEnd
- delayLongPress
- delayPressStart
- delayPressEnd
- pressRententionOffset / pressRect
- emulated mouse events
-
fix keyboard press events when metaKey is involved(this currently works the same way as native, so will leave it for now) - any special case anchor tag-related tests
- hitslop interactions
- Determine event object data.
{
pointerType: 'mouse' | 'pen' | 'touch' | 'keyboard',
initial: { x: number, y: number },
point: { x: number, y: number },
delta: { x: number, y: number }
}
FocusScope module
Consider adding onFocusIn
and onFocusOut
(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 tabIndex={-1}
.
InputScope module
-
onChange
fires when an input element has been changed. This applies to<input>
,<textarea>
,<select>
and elements withcontenteditable
or whendesignMode
is enabled.onChange
provides a callback with the event for the element that changed and akey
of the elment that changed (if akey
was supplied). -
onValueChange
is similar toonChange
, but only provides the value changed. This applies to<input>
,<textarea>
,<select>
and elements withcontenteditable
or whendesignMode
is enabled.onValueChange
provides avalue
andkey
of the element that changed (if akey
was supplied). -
onSubmit
for when any<form>
elements trigger form submit. -
onKeyPress
for when any keyboard keys are pressed. -
preventKeys
accepts an array of key strings that will get prevented from entering input. -
onSelectionChange
for when any any text selection occurs in any child elements. -
onBeforeChange
fires before a change is about to occur. This applies to<input>
,<textarea>
,<select>
and elements withcontenteditable
or whendesignMode
is enabled.onBeforeChange
provides a callback with the event for the element that changed and akey
of the elment that changed (if akey
was supplied).
Drag module
- Determine event object data (same as Pan)
- Cancelling drag.
- FYI:
touchAction:'none'
is needed oncurrentTarget
to prevent browser cancelling afterpointermove
. - FYI: Firefox might have problems when combining
mousemove
withdisplay:flex
. - Add README
- Cross browser and device/modality testing.
- Write unit tests.
Pan module
- Write unit tests.
- Cross browser and device/modality testing.
- Cancelling pan.
- Add README
- FYI:
touchAction:'none'
is needed oncurrentTarget
to prevent browser cancelling afterpointermove
. - Determine event object data.
{
pointerType: 'mouse' | 'pen' | 'touch',
initial: { x: number, y: number },
point: { x: number, y: number },
velocity: { x: number, y: number },
delta: { x: number, y: number }
}
Scroll module
-
disabled
-
onScroll
-
onScrollDragStart
-
onScrollDragEnd
-
onMomentumScrollStart
-
onMomentumScrollEnd
- scroll directions
- Determine event object data
- FYI Fix wheel/touch browser locking in IE and Safari #9333
Swipe module
- Combine
onSwipe{Left,Right,Up,Down}
intoonSwipe
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:
touchAction:'none'
is needed oncurrentTarget
to prevent browser cancelling afterpointermove
.
Touch HitSlop
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
position
parent of hitslop. - Measurement without reflows (ResizeObserver?)
- Add README
- Figure out solution for SSR w/ non-touch interactions if no client-side JS
- Add
touchHitSlop
(Add event touch hit target hit slop logic to experimental event API #15261). - Add
touchHitSlop
SSR support
Dev Tools (#15267)
- Add
displayName
fields to event components and event targets. - Possibly add a
description
ordisplayName
field to event responders? For example thePress
responder module for ReactDOM could have the nameReactDOMPressResponder
. - Expose some DEV only event triggering exports from event responder modules. i.e.
HoverEventResponder.DevToolEvents = [{ name: 'hover', trigger: [{ name: 'pointerup', passive: false }]];
- Add basic support for rendering event responders and event targets in the tree. @bvaughn
Ancillary work
- Investigate removing object assign polyfill from individual event modules
- Add internal interactive documentation / fiddle
- Investigate press event patterns on
input
,textarea
,select
, etc. - Implement high-level components like Pressable (inc delays).
- Nested Pressables should not bubble events.
- Investigate accounting for element resizes when determining hit bounds, e.g., using resize observer for notifications