mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Experimental Event API: Press event properties (#15586)
This commit is contained in:
Vendored
+143
-34
@@ -10,11 +10,9 @@
|
||||
import type {
|
||||
ReactResponderEvent,
|
||||
ReactResponderContext,
|
||||
ReactResponderDispatchEventOptions,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {isEventPositionWithinTouchHitTarget} from './utils';
|
||||
|
||||
type PressProps = {
|
||||
@@ -86,6 +84,13 @@ type PressEvent = {|
|
||||
target: Element | Document,
|
||||
type: PressEventType,
|
||||
pointerType: PointerType,
|
||||
timeStamp: null | number,
|
||||
clientX: null | number,
|
||||
clientY: null | number,
|
||||
pageX: null | number,
|
||||
pageY: null | number,
|
||||
screenX: null | number,
|
||||
screenY: null | number,
|
||||
|};
|
||||
|
||||
const DEFAULT_PRESS_END_DELAY_MS = 0;
|
||||
@@ -133,58 +138,112 @@ function createPressEvent(
|
||||
type: PressEventType,
|
||||
target: Element | Document,
|
||||
pointerType: PointerType,
|
||||
event: ?ReactResponderEvent,
|
||||
delay: number,
|
||||
): PressEvent {
|
||||
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;
|
||||
if (nativeEvent.clientX !== undefined) {
|
||||
eventObject = (nativeEvent: any);
|
||||
} else if (isTouchEvent(nativeEvent)) {
|
||||
eventObject = getTouchFromPressEvent(nativeEvent);
|
||||
}
|
||||
if (eventObject) {
|
||||
({clientX, clientY, pageX, pageY, screenX, screenY} = eventObject);
|
||||
}
|
||||
}
|
||||
return {
|
||||
target,
|
||||
type,
|
||||
pointerType,
|
||||
timeStamp,
|
||||
clientX,
|
||||
clientY,
|
||||
pageX,
|
||||
pageY,
|
||||
screenX,
|
||||
screenY,
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchEvent(
|
||||
event: ?ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
state: PressState,
|
||||
name: PressEventType,
|
||||
listener: (e: Object) => void,
|
||||
options?: ReactResponderDispatchEventOptions,
|
||||
discrete: boolean,
|
||||
delay: number,
|
||||
): void {
|
||||
const target = ((state.pressTarget: any): Element | Document);
|
||||
const pointerType = state.pointerType;
|
||||
const syntheticEvent = createPressEvent(name, target, pointerType);
|
||||
context.dispatchEvent(
|
||||
syntheticEvent,
|
||||
listener,
|
||||
options || {
|
||||
discrete: true,
|
||||
},
|
||||
const syntheticEvent = createPressEvent(
|
||||
name,
|
||||
target,
|
||||
pointerType,
|
||||
event,
|
||||
delay,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, listener, {
|
||||
discrete,
|
||||
});
|
||||
}
|
||||
|
||||
function dispatchPressChangeEvent(
|
||||
event: ?ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
delay: number,
|
||||
): void {
|
||||
const bool = state.isActivePressed;
|
||||
const listener = () => {
|
||||
props.onPressChange(bool);
|
||||
};
|
||||
dispatchEvent(context, state, 'presschange', listener);
|
||||
dispatchEvent(event, context, state, 'presschange', listener, true, delay);
|
||||
}
|
||||
|
||||
function dispatchLongPressChangeEvent(
|
||||
event: ?ReactResponderEvent,
|
||||
context: ReactResponderContext,
|
||||
props: PressProps,
|
||||
state: PressState,
|
||||
delay: number,
|
||||
): void {
|
||||
const bool = state.isLongPressed;
|
||||
const listener = () => {
|
||||
props.onLongPressChange(bool);
|
||||
};
|
||||
dispatchEvent(context, state, 'longpresschange', listener);
|
||||
dispatchEvent(
|
||||
event,
|
||||
context,
|
||||
state,
|
||||
'longpresschange',
|
||||
listener,
|
||||
true,
|
||||
delay,
|
||||
);
|
||||
}
|
||||
|
||||
function activate(event, context, props, state) {
|
||||
function activate(
|
||||
event: ReactResponderEvent,
|
||||
context,
|
||||
props,
|
||||
state,
|
||||
delay: number,
|
||||
) {
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const {x, y} = getEventPageCoords(nativeEvent);
|
||||
const wasActivePressed = state.isActivePressed;
|
||||
@@ -197,26 +256,48 @@ function activate(event, context, props, state) {
|
||||
}
|
||||
|
||||
if (props.onPressStart) {
|
||||
dispatchEvent(context, state, 'pressstart', props.onPressStart);
|
||||
dispatchEvent(
|
||||
event,
|
||||
context,
|
||||
state,
|
||||
'pressstart',
|
||||
props.onPressStart,
|
||||
true,
|
||||
delay,
|
||||
);
|
||||
}
|
||||
if (!wasActivePressed && props.onPressChange) {
|
||||
dispatchPressChangeEvent(context, props, state);
|
||||
dispatchPressChangeEvent(event, context, props, state, delay);
|
||||
}
|
||||
}
|
||||
|
||||
function deactivate(context, props, state) {
|
||||
function deactivate(
|
||||
event: ?ReactResponderEvent,
|
||||
context,
|
||||
props,
|
||||
state,
|
||||
delay: number,
|
||||
) {
|
||||
const wasLongPressed = state.isLongPressed;
|
||||
state.isActivePressed = false;
|
||||
state.isLongPressed = false;
|
||||
|
||||
if (props.onPressEnd) {
|
||||
dispatchEvent(context, state, 'pressend', props.onPressEnd);
|
||||
dispatchEvent(
|
||||
event,
|
||||
context,
|
||||
state,
|
||||
'pressend',
|
||||
props.onPressEnd,
|
||||
true,
|
||||
delay,
|
||||
);
|
||||
}
|
||||
if (props.onPressChange) {
|
||||
dispatchPressChangeEvent(context, props, state);
|
||||
dispatchPressChangeEvent(event, context, props, state, delay);
|
||||
}
|
||||
if (wasLongPressed && props.onLongPressChange) {
|
||||
dispatchLongPressChangeEvent(context, props, state);
|
||||
dispatchLongPressChangeEvent(event, context, props, state, delay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,9 +314,9 @@ function dispatchPressStartEvents(
|
||||
state.pressEndTimeout = null;
|
||||
}
|
||||
|
||||
const dispatch = () => {
|
||||
const dispatch = (delay: number) => {
|
||||
state.isActivePressStart = true;
|
||||
activate(event, context, props, state);
|
||||
activate(event, context, props, state, delay);
|
||||
|
||||
if (
|
||||
(props.onLongPress || props.onLongPressChange) &&
|
||||
@@ -250,10 +331,24 @@ function dispatchPressStartEvents(
|
||||
state.isLongPressed = true;
|
||||
state.longPressTimeout = null;
|
||||
if (props.onLongPress) {
|
||||
dispatchEvent(context, state, 'longpress', props.onLongPress);
|
||||
dispatchEvent(
|
||||
event,
|
||||
context,
|
||||
state,
|
||||
'longpress',
|
||||
props.onLongPress,
|
||||
true,
|
||||
delayLongPress + delay,
|
||||
);
|
||||
}
|
||||
if (props.onLongPressChange) {
|
||||
dispatchLongPressChangeEvent(context, props, state);
|
||||
dispatchLongPressChangeEvent(
|
||||
event,
|
||||
context,
|
||||
props,
|
||||
state,
|
||||
delayLongPress + delay,
|
||||
);
|
||||
}
|
||||
}, delayLongPress);
|
||||
}
|
||||
@@ -268,10 +363,10 @@ function dispatchPressStartEvents(
|
||||
if (delayPressStart > 0) {
|
||||
state.pressStartTimeout = context.setTimeout(() => {
|
||||
state.pressStartTimeout = null;
|
||||
dispatch();
|
||||
dispatch(delayPressStart);
|
||||
}, delayPressStart);
|
||||
} else {
|
||||
dispatch();
|
||||
dispatch(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,7 +394,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);
|
||||
activate(event, context, props, state, 0);
|
||||
activationWasForced = true;
|
||||
}
|
||||
}
|
||||
@@ -316,10 +411,10 @@ function dispatchPressEndEvents(
|
||||
if (delayPressEnd > 0) {
|
||||
state.pressEndTimeout = context.setTimeout(() => {
|
||||
state.pressEndTimeout = null;
|
||||
deactivate(context, props, state);
|
||||
deactivate(event, context, props, state, delayPressEnd);
|
||||
}, delayPressEnd);
|
||||
} else {
|
||||
deactivate(context, props, state);
|
||||
deactivate(event, context, props, state, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,8 +463,8 @@ function calculateResponderRegion(
|
||||
) {
|
||||
const pressRetentionOffset = context.objectAssign(
|
||||
{},
|
||||
...DEFAULT_PRESS_RETENTION_OFFSET,
|
||||
...props.pressRetentionOffset,
|
||||
DEFAULT_PRESS_RETENTION_OFFSET,
|
||||
props.pressRetentionOffset,
|
||||
);
|
||||
|
||||
const clientRect = target.getBoundingClientRect();
|
||||
@@ -656,9 +751,15 @@ const PressResponder = {
|
||||
if (state.isPressWithinResponderRegion) {
|
||||
if (state.isPressed) {
|
||||
if (props.onPressMove) {
|
||||
dispatchEvent(context, state, 'pressmove', props.onPressMove, {
|
||||
discrete: false,
|
||||
});
|
||||
dispatchEvent(
|
||||
event,
|
||||
context,
|
||||
state,
|
||||
'pressmove',
|
||||
props.onPressMove,
|
||||
false,
|
||||
0,
|
||||
);
|
||||
}
|
||||
if (
|
||||
state.activationPosition != null &&
|
||||
@@ -735,7 +836,15 @@ const PressResponder = {
|
||||
props.onLongPressShouldCancelPress()
|
||||
)
|
||||
) {
|
||||
dispatchEvent(context, state, 'press', props.onPress);
|
||||
dispatchEvent(
|
||||
event,
|
||||
context,
|
||||
state,
|
||||
'press',
|
||||
props.onPress,
|
||||
true,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2053,6 +2053,159 @@ describe('Event responder: Press', () => {
|
||||
ref.current.dispatchEvent(createEvent('pointermove'));
|
||||
ref.current.dispatchEvent(createEvent('pointerup'));
|
||||
ref.current.dispatchEvent(createEvent('pointerdown'));
|
||||
});
|
||||
|
||||
it('should correctly pass through event properties', () => {
|
||||
const timeStamps = [];
|
||||
const ref = React.createRef();
|
||||
const eventLog = [];
|
||||
const logEvent = event => {
|
||||
const propertiesWeCareAbout = {
|
||||
pageX: event.pageX,
|
||||
pageY: event.pageY,
|
||||
screenX: event.screenX,
|
||||
screenY: event.screenY,
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
pointerType: event.pointerType,
|
||||
target: event.target,
|
||||
timeStamp: event.timeStamp,
|
||||
type: event.type,
|
||||
};
|
||||
timeStamps.push(event.timeStamp);
|
||||
eventLog.push(propertiesWeCareAbout);
|
||||
};
|
||||
const element = (
|
||||
<Press
|
||||
onPressStart={logEvent}
|
||||
onPressEnd={logEvent}
|
||||
onPressMove={logEvent}
|
||||
onLongPress={logEvent}
|
||||
onPress={logEvent}>
|
||||
<button ref={ref} />
|
||||
</Press>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('pointerdown', {
|
||||
pointerType: 'mouse',
|
||||
pageX: 15,
|
||||
pageY: 16,
|
||||
screenX: 20,
|
||||
screenY: 21,
|
||||
clientX: 30,
|
||||
clientY: 31,
|
||||
}),
|
||||
);
|
||||
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('pointermove', {
|
||||
pointerType: 'mouse',
|
||||
pageX: 16,
|
||||
pageY: 17,
|
||||
screenX: 21,
|
||||
screenY: 22,
|
||||
clientX: 31,
|
||||
clientY: 32,
|
||||
}),
|
||||
);
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('pointerup', {
|
||||
pointerType: 'mouse',
|
||||
pageX: 17,
|
||||
pageY: 18,
|
||||
screenX: 22,
|
||||
screenY: 23,
|
||||
clientX: 32,
|
||||
clientY: 33,
|
||||
}),
|
||||
);
|
||||
ref.current.dispatchEvent(
|
||||
createEvent('pointerdown', {
|
||||
pointerType: 'mouse',
|
||||
pageX: 18,
|
||||
pageY: 19,
|
||||
screenX: 23,
|
||||
screenY: 24,
|
||||
clientX: 33,
|
||||
clientY: 34,
|
||||
}),
|
||||
);
|
||||
expect(typeof timeStamps[0] === 'number').toBe(true);
|
||||
expect(eventLog).toEqual([
|
||||
{
|
||||
pointerType: 'mouse',
|
||||
pageX: 15,
|
||||
pageY: 16,
|
||||
screenX: 20,
|
||||
screenY: 21,
|
||||
clientX: 30,
|
||||
clientY: 31,
|
||||
target: ref.current,
|
||||
timeStamp: timeStamps[0],
|
||||
type: 'pressstart',
|
||||
},
|
||||
{
|
||||
pointerType: 'mouse',
|
||||
pageX: 15,
|
||||
pageY: 16,
|
||||
screenX: 20,
|
||||
screenY: 21,
|
||||
clientX: 30,
|
||||
clientY: 31,
|
||||
target: ref.current,
|
||||
timeStamp: timeStamps[0] + DEFAULT_LONG_PRESS_DELAY,
|
||||
type: 'longpress',
|
||||
},
|
||||
{
|
||||
pointerType: 'mouse',
|
||||
pageX: 16,
|
||||
pageY: 17,
|
||||
screenX: 21,
|
||||
screenY: 22,
|
||||
clientX: 31,
|
||||
clientY: 32,
|
||||
target: ref.current,
|
||||
timeStamp: timeStamps[2],
|
||||
type: 'pressmove',
|
||||
},
|
||||
{
|
||||
pointerType: 'mouse',
|
||||
pageX: 17,
|
||||
pageY: 18,
|
||||
screenX: 22,
|
||||
screenY: 23,
|
||||
clientX: 32,
|
||||
clientY: 33,
|
||||
target: ref.current,
|
||||
timeStamp: timeStamps[3],
|
||||
type: 'pressend',
|
||||
},
|
||||
{
|
||||
pointerType: 'mouse',
|
||||
pageX: 17,
|
||||
pageY: 18,
|
||||
screenX: 22,
|
||||
screenY: 23,
|
||||
clientX: 32,
|
||||
clientY: 33,
|
||||
target: ref.current,
|
||||
timeStamp: timeStamps[3],
|
||||
type: 'press',
|
||||
},
|
||||
{
|
||||
pointerType: 'mouse',
|
||||
pageX: 18,
|
||||
pageY: 19,
|
||||
screenX: 23,
|
||||
screenY: 24,
|
||||
clientX: 33,
|
||||
clientY: 34,
|
||||
target: ref.current,
|
||||
timeStamp: timeStamps[5],
|
||||
type: 'pressstart',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user