diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index cd83864fbb1c2..0f47f72a4f9ae 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -64,6 +64,7 @@ type ResponderTimer = {| instance: ReactEventComponentInstance, func: () => void, id: Symbol, + timeStamp: number, |}; const activeTimeouts: Map = new Map(); @@ -90,6 +91,7 @@ const responderOwners: Map< > = new Map(); let globalOwner = null; +let currentTimeStamp = 0; let currentTimers = new Map(); let currentInstance: null | ReactEventComponentInstance = null; let currentEventQueue: null | EventQueue = null; @@ -101,11 +103,11 @@ const eventResponderContext: ReactResponderContext = { {discrete}: ReactResponderDispatchEventOptions, ): void { validateResponderContext(); - const {target, type} = possibleEventObject; + const {target, type, timeStamp} = possibleEventObject; - if (target == null || type == null) { + if (target == null || type == null || timeStamp == null) { throw new Error( - 'context.dispatchEvent: "target" and "type" fields on event object are required.', + 'context.dispatchEvent: "target", "timeStamp", and "type" fields on event object are required.', ); } if (__DEV__) { @@ -313,7 +315,7 @@ const eventResponderContext: ReactResponderContext = { if (timeout === undefined) { const timers = new Map(); const id = setTimeout(() => { - processTimers(timers); + processTimers(timers, delay); }, delay); timeout = { id, @@ -325,6 +327,7 @@ const eventResponderContext: ReactResponderContext = { instance: ((currentInstance: any): ReactEventComponentInstance), func, id: timerId, + timeStamp: currentTimeStamp, }); activeTimeouts.set(timerId, timeout); return timerId; @@ -408,6 +411,9 @@ const eventResponderContext: ReactResponderContext = { } return currentTarget; }, + getTimeStamp(): number { + return currentTimeStamp; + }, }; function isTargetWithinEventComponent(target: Element | Document): boolean { @@ -478,13 +484,17 @@ function isFiberHostComponentFocusable(fiber: Fiber): boolean { ); } -function processTimers(timers: Map): void { +function processTimers( + timers: Map, + delay: number, +): void { const timersArr = Array.from(timers.values()); currentEventQueue = createEventQueue(); try { for (let i = 0; i < timersArr.length; i++) { - const {instance, func, id} = timersArr[i]; + const {instance, func, id, timeStamp} = timersArr[i]; currentInstance = instance; + currentTimeStamp = timeStamp + delay; try { func(); } finally { @@ -496,6 +506,7 @@ function processTimers(timers: Map): void { currentTimers = null; currentInstance = null; currentEventQueue = null; + currentTimeStamp = 0; } } @@ -861,8 +872,11 @@ export function dispatchEventForResponderEventSystem( const previousEventQueue = currentEventQueue; const previousInstance = currentInstance; const previousTimers = currentTimers; + const previousTimeStamp = currentTimeStamp; currentTimers = null; currentEventQueue = createEventQueue(); + // We might want to control timeStamp another way here + currentTimeStamp = (nativeEvent: any).timeStamp; try { traverseAndHandleEventResponderInstances( topLevelType, @@ -876,6 +890,7 @@ export function dispatchEventForResponderEventSystem( currentTimers = previousTimers; currentInstance = previousInstance; currentEventQueue = previousEventQueue; + currentTimeStamp = previousTimeStamp; } } } diff --git a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js index bdcf893f20054..40ecaaeba7260 100644 --- a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js @@ -422,6 +422,7 @@ describe('DOMEventResponderSystem', () => { target: event.target, type: 'magicclick', phase: 'bubble', + timeStamp: context.getTimeStamp(), }; context.dispatchEvent(syntheticEvent, props.onMagicClick, { discrete: true, @@ -434,6 +435,7 @@ describe('DOMEventResponderSystem', () => { target: event.target, type: 'magicclick', phase: 'capture', + timeStamp: context.getTimeStamp(), }; context.dispatchEvent(syntheticEvent, props.onMagicClick, { discrete: true, @@ -477,6 +479,7 @@ describe('DOMEventResponderSystem', () => { target: event.target, type: 'press', phase, + timeStamp: context.getTimeStamp(), }; context.dispatchEvent(pressEvent, props.onPress, {discrete: true}); @@ -486,6 +489,7 @@ describe('DOMEventResponderSystem', () => { target: event.target, type: 'longpress', phase, + timeStamp: context.getTimeStamp(), }; context.dispatchEvent(longPressEvent, props.onLongPress, { discrete: true, @@ -497,6 +501,7 @@ describe('DOMEventResponderSystem', () => { target: event.target, type: 'longpresschange', phase, + timeStamp: context.getTimeStamp(), }; context.dispatchEvent(longPressChangeEvent, props.onLongPressChange, { discrete: true, @@ -901,6 +906,7 @@ describe('DOMEventResponderSystem', () => { const syntheticEvent = { target: event.target, type: 'click', + timeStamp: context.getTimeStamp(), }; context.dispatchEvent(syntheticEvent, props.onClick, { discrete: true, diff --git a/packages/react-events/src/Drag.js b/packages/react-events/src/Drag.js index bbc9fa7627ffb..c396caed766f4 100644 --- a/packages/react-events/src/Drag.js +++ b/packages/react-events/src/Drag.js @@ -50,11 +50,13 @@ type DragEventType = 'dragstart' | 'dragend' | 'dragchange' | 'dragmove'; type DragEvent = {| target: Element | Document, type: DragEventType, + timeStamp: number, diffX?: number, diffY?: number, |}; function createDragEvent( + context: ReactResponderContext, type: DragEventType, target: Element | Document, eventData?: EventData, @@ -62,6 +64,7 @@ function createDragEvent( return { target, type, + timeStamp: context.getTimeStamp(), ...eventData, }; } @@ -75,7 +78,7 @@ function dispatchDragEvent( eventData?: EventData, ): void { const target = ((state.dragTarget: any): Element | Document); - const syntheticEvent = createDragEvent(name, target, eventData); + const syntheticEvent = createDragEvent(context, name, target, eventData); context.dispatchEvent(syntheticEvent, listener, {discrete}); } diff --git a/packages/react-events/src/Focus.js b/packages/react-events/src/Focus.js index 2583c79a8cc3a..7f4664b87b251 100644 --- a/packages/react-events/src/Focus.js +++ b/packages/react-events/src/Focus.js @@ -33,6 +33,7 @@ type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange'; type FocusEvent = {| target: Element | Document, type: FocusEventType, + timeStamp: number, |}; const targetEventTypes = [ @@ -56,12 +57,14 @@ const rootEventTypes = [ ]; function createFocusEvent( + context: ReactResponderContext, type: FocusEventType, target: Element | Document, ): FocusEvent { return { target, type, + timeStamp: context.getTimeStamp(), }; } @@ -72,21 +75,25 @@ function dispatchFocusInEvents( ) { const target = ((state.focusTarget: any): Element | Document); if (props.onFocus) { - const syntheticEvent = createFocusEvent('focus', target); + const syntheticEvent = createFocusEvent(context, 'focus', target); context.dispatchEvent(syntheticEvent, props.onFocus, {discrete: true}); } if (props.onFocusChange) { const listener = () => { props.onFocusChange(true); }; - const syntheticEvent = createFocusEvent('focuschange', target); + const syntheticEvent = createFocusEvent(context, 'focuschange', target); context.dispatchEvent(syntheticEvent, listener, {discrete: true}); } if (props.onFocusVisibleChange && state.isLocalFocusVisible) { const listener = () => { props.onFocusVisibleChange(true); }; - const syntheticEvent = createFocusEvent('focusvisiblechange', target); + const syntheticEvent = createFocusEvent( + context, + 'focusvisiblechange', + target, + ); context.dispatchEvent(syntheticEvent, listener, {discrete: true}); } } @@ -98,14 +105,14 @@ function dispatchFocusOutEvents( ) { const target = ((state.focusTarget: any): Element | Document); if (props.onBlur) { - const syntheticEvent = createFocusEvent('blur', target); + const syntheticEvent = createFocusEvent(context, 'blur', target); context.dispatchEvent(syntheticEvent, props.onBlur, {discrete: true}); } if (props.onFocusChange) { const listener = () => { props.onFocusChange(false); }; - const syntheticEvent = createFocusEvent('focuschange', target); + const syntheticEvent = createFocusEvent(context, 'focuschange', target); context.dispatchEvent(syntheticEvent, listener, {discrete: true}); } dispatchFocusVisibleOutEvent(context, props, state); @@ -121,7 +128,11 @@ function dispatchFocusVisibleOutEvent( const listener = () => { props.onFocusVisibleChange(false); }; - const syntheticEvent = createFocusEvent('focusvisiblechange', target); + const syntheticEvent = createFocusEvent( + context, + 'focusvisiblechange', + target, + ); context.dispatchEvent(syntheticEvent, listener, {discrete: true}); state.isLocalFocusVisible = false; } diff --git a/packages/react-events/src/Hover.js b/packages/react-events/src/Hover.js index 97bf1a824b242..e89970ca89aa6 100644 --- a/packages/react-events/src/Hover.js +++ b/packages/react-events/src/Hover.js @@ -42,6 +42,7 @@ type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove'; type HoverEvent = {| target: Element | Document, type: HoverEventType, + timeStamp: number, |}; const DEFAULT_HOVER_END_DELAY_MS = 0; @@ -60,12 +61,14 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) { } function createHoverEvent( + context: ReactResponderContext, type: HoverEventType, target: Element | Document, ): HoverEvent { return { target, type, + timeStamp: context.getTimeStamp(), }; } @@ -79,6 +82,7 @@ function dispatchHoverChangeEvent( props.onHoverChange(bool); }; const syntheticEvent = createHoverEvent( + context, 'hoverchange', ((state.hoverTarget: any): Element | Document), ); @@ -115,6 +119,7 @@ function dispatchHoverStartEvents( if (props.onHoverStart) { const syntheticEvent = createHoverEvent( + context, 'hoverstart', ((target: any): Element | Document), ); @@ -174,6 +179,7 @@ function dispatchHoverEndEvents( if (props.onHoverEnd) { const syntheticEvent = createHoverEvent( + context, 'hoverend', ((target: any): Element | Document), ); @@ -311,6 +317,7 @@ const HoverResponder = { } else { if (props.onHoverMove && state.hoverTarget !== null) { const syntheticEvent = createHoverEvent( + context, 'hovermove', state.hoverTarget, ); diff --git a/packages/react-events/src/Press.js b/packages/react-events/src/Press.js index f11a07daa8900..5da2ee049dc82 100644 --- a/packages/react-events/src/Press.js +++ b/packages/react-events/src/Press.js @@ -84,7 +84,7 @@ type PressEvent = {| target: Element | Document, type: PressEventType, pointerType: PointerType, - timeStamp: null | number, + timeStamp: number, clientX: null | number, clientY: null | number, pageX: null | number, @@ -135,23 +135,22 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) { } function createPressEvent( + context: ReactResponderContext, type: PressEventType, target: Element | Document, pointerType: PointerType, event: ?ReactResponderEvent, - delay: number, ): PressEvent { + const timeStamp = context.getTimeStamp(); let clientX = null; let clientY = null; let pageX = null; let pageY = null; let screenX = null; let screenY = null; - let timeStamp = null; if (event) { const nativeEvent = (event.nativeEvent: any); - timeStamp = nativeEvent.timeStamp + delay; // Only check for one property, checking for all of them is costly. We can assume // if clientX exists, so do the rest. let eventObject; @@ -185,16 +184,15 @@ function dispatchEvent( name: PressEventType, listener: (e: Object) => void, discrete: boolean, - delay: number, ): void { const target = ((state.pressTarget: any): Element | Document); const pointerType = state.pointerType; const syntheticEvent = createPressEvent( + context, name, target, pointerType, event, - delay, ); context.dispatchEvent(syntheticEvent, listener, { discrete, @@ -206,13 +204,12 @@ function dispatchPressChangeEvent( context: ReactResponderContext, props: PressProps, state: PressState, - delay: number, ): void { const bool = state.isActivePressed; const listener = () => { props.onPressChange(bool); }; - dispatchEvent(event, context, state, 'presschange', listener, true, delay); + dispatchEvent(event, context, state, 'presschange', listener, true); } function dispatchLongPressChangeEvent( @@ -220,30 +217,15 @@ function dispatchLongPressChangeEvent( context: ReactResponderContext, props: PressProps, state: PressState, - delay: number, ): void { const bool = state.isLongPressed; const listener = () => { props.onLongPressChange(bool); }; - dispatchEvent( - event, - context, - state, - 'longpresschange', - listener, - true, - delay, - ); + dispatchEvent(event, context, state, 'longpresschange', listener, true); } -function activate( - event: ReactResponderEvent, - context, - props, - state, - delay: number, -) { +function activate(event: ReactResponderEvent, context, props, state) { const nativeEvent: any = event.nativeEvent; const {x, y} = getEventPageCoords(nativeEvent); const wasActivePressed = state.isActivePressed; @@ -263,41 +245,26 @@ function activate( 'pressstart', props.onPressStart, true, - delay, ); } if (!wasActivePressed && props.onPressChange) { - dispatchPressChangeEvent(event, context, props, state, delay); + dispatchPressChangeEvent(event, context, props, state); } } -function deactivate( - event: ?ReactResponderEvent, - context, - props, - state, - delay: number, -) { +function deactivate(event: ?ReactResponderEvent, context, props, state) { const wasLongPressed = state.isLongPressed; state.isActivePressed = false; state.isLongPressed = false; if (props.onPressEnd) { - dispatchEvent( - event, - context, - state, - 'pressend', - props.onPressEnd, - true, - delay, - ); + dispatchEvent(event, context, state, 'pressend', props.onPressEnd, true); } if (props.onPressChange) { - dispatchPressChangeEvent(event, context, props, state, delay); + dispatchPressChangeEvent(event, context, props, state); } if (wasLongPressed && props.onLongPressChange) { - dispatchLongPressChangeEvent(event, context, props, state, delay); + dispatchLongPressChangeEvent(event, context, props, state); } } @@ -314,9 +281,9 @@ function dispatchPressStartEvents( state.pressEndTimeout = null; } - const dispatch = (delay: number) => { + const dispatch = () => { state.isActivePressStart = true; - activate(event, context, props, state, delay); + activate(event, context, props, state); if ( (props.onLongPress || props.onLongPressChange) && @@ -338,17 +305,10 @@ function dispatchPressStartEvents( 'longpress', props.onLongPress, true, - delayLongPress + delay, ); } if (props.onLongPressChange) { - dispatchLongPressChangeEvent( - event, - context, - props, - state, - delayLongPress + delay, - ); + dispatchLongPressChangeEvent(event, context, props, state); } }, delayLongPress); } @@ -363,10 +323,10 @@ function dispatchPressStartEvents( if (delayPressStart > 0) { state.pressStartTimeout = context.setTimeout(() => { state.pressStartTimeout = null; - dispatch(delayPressStart); + dispatch(); }, delayPressStart); } else { - dispatch(0); + dispatch(); } } } @@ -394,7 +354,7 @@ function dispatchPressEndEvents( // don't activate if a press has moved beyond the responder region if (state.isPressWithinResponderRegion && event != null) { // if we haven't yet activated (due to delays), activate now - activate(event, context, props, state, 0); + activate(event, context, props, state); activationWasForced = true; } } @@ -411,10 +371,10 @@ function dispatchPressEndEvents( if (delayPressEnd > 0) { state.pressEndTimeout = context.setTimeout(() => { state.pressEndTimeout = null; - deactivate(event, context, props, state, delayPressEnd); + deactivate(event, context, props, state); }, delayPressEnd); } else { - deactivate(event, context, props, state, 0); + deactivate(event, context, props, state); } } } @@ -758,7 +718,6 @@ const PressResponder = { 'pressmove', props.onPressMove, false, - 0, ); } if ( @@ -843,7 +802,6 @@ const PressResponder = { 'press', props.onPress, true, - 0, ); } } diff --git a/packages/react-events/src/Swipe.js b/packages/react-events/src/Swipe.js index 6a9595754c1d8..f402b8fc7595f 100644 --- a/packages/react-events/src/Swipe.js +++ b/packages/react-events/src/Swipe.js @@ -40,11 +40,13 @@ type SwipeEventType = 'swipeleft' | 'swiperight' | 'swipeend' | 'swipemove'; type SwipeEvent = {| target: Element | Document, type: SwipeEventType, + timeStamp: number, diffX?: number, diffY?: number, |}; function createSwipeEvent( + context: ReactResponderContext, type: SwipeEventType, target: Element | Document, eventData?: EventData, @@ -52,6 +54,7 @@ function createSwipeEvent( return { target, type, + timeStamp: context.getTimeStamp(), ...eventData, }; } @@ -65,7 +68,7 @@ function dispatchSwipeEvent( eventData?: EventData, ) { const target = ((state.swipeTarget: any): Element | Document); - const syntheticEvent = createSwipeEvent(name, target, eventData); + const syntheticEvent = createSwipeEvent(context, name, target, eventData); context.dispatchEvent(syntheticEvent, listener, {discrete}); } diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index fad4401638429..7a90c0f80252c 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -193,4 +193,5 @@ export type ReactResponderContext = { event: ReactResponderEvent, ): '' | 'mouse' | 'keyboard' | 'pen' | 'touch', getEventCurrentTarget(event: ReactResponderEvent): Element, + getTimeStamp: () => number, };