[Flare] Remove longpress from press responder (#16242)

Long press will move to a separate responder.
This commit is contained in:
Nicolas Gallagher
2019-07-29 13:12:12 -07:00
committed by GitHub
parent 9914a19190
commit 47656bf2a1
3 changed files with 24 additions and 467 deletions

View File

@@ -3,9 +3,9 @@
The `Press` module responds to press events on the element it wraps. Press
events are dispatched for `mouse`, `pen`, `touch`, `trackpad`, and `keyboard`
pointer types. Press events are only dispatched for keyboards when pressing the
Enter or Spacebar keys. If neither `onPress` nor `onLongPress` are called, this
signifies that the press ended outside of the element hit bounds (i.e., the user
aborted the press).
Enter or Spacebar keys. If `onPress` is not called, this signifies that the
press ended outside of the element hit bounds (i.e., the user aborted the
press).
Press events do not propagate between `Press` event responders.
@@ -17,7 +17,6 @@ const Button = (props) => (
<Press
onPress={props.onPress}
onPressChange={setPressed}
onLongPress={props.onLongPress}
>
<div
{...props}
@@ -60,8 +59,6 @@ type PressEvent = {
| 'pressend'
| 'presschange'
| 'pressmove'
| 'longpress'
| 'longpresschange'
| 'contextmenu',
x: number,
y: number
@@ -77,10 +74,6 @@ type PressOffset = {
## Props
### delayLongPress: number = 500ms
The duration of a press before `onLongPress` and `onLongPressChange` are called.
### delayPressEnd: number
The duration of the delay between when the press ends and when `onPressEnd` is
@@ -101,27 +94,10 @@ Disables all `Press` events.
Called when the context menu is shown. When a press is active, the context menu
will only be shown (and the press cancelled) if `preventDefault` is `false`.
### onLongPress: (e: PressEvent) => void
Called once the element has been pressed for the length of `delayLongPress`. If
the press point moves more than 10px `onLongPress` is cancelled.
### onLongPressChange: boolean => void
Called when the element changes long-press state.
### longPressShouldCancelPress: () => boolean
Determines whether calling `onPress` should be cancelled if `onLongPress` or
`onLongPressChange` have already been called. Default is `false`.
### onPress: (e: PressEvent) => void
Called immediately after a press is released, unless either 1) the press is
released outside the hit bounds of the element (accounting for
`pressRetentionOffset`), or 2) the press was a long press,
and `onLongPress` or `onLongPressChange` props are provided, and
`onLongPressCancelsPress()` is `true`.
Called immediately after a press is released, unless the press is released
outside the hit bounds of the element (accounting for `pressRetentionOffset`.
### onPressChange: boolean => void
@@ -164,7 +140,6 @@ is still called.
Whether to `preventDefault()` native events. Native behavior is prevented by
default. If an anchor is the child of `Press`, internal and external navigation
should be performed in `onPress`/`onLongPress`. To rely on native behavior
instead, set `preventDefault` to `false`, but be aware that native behavior will
take place immediately after interaction without respect for delays or long
press.
should be performed in `onPress`. To rely on native behavior instead, set
`preventDefault` to `false`, but be aware that native behavior will take place
immediately after interaction without respect for delays or long press.

View File

@@ -19,8 +19,6 @@ import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
type PressListenerProps = {|
onContextMenu: (e: PressEvent) => void,
onLongPress: (e: PressEvent) => void,
onLongPressChange: boolean => void,
onPress: (e: PressEvent) => void,
onPressChange: boolean => void,
onPressEnd: (e: PressEvent) => void,
@@ -30,7 +28,6 @@ type PressListenerProps = {|
type PressProps = {|
disabled: boolean,
delayLongPress: number,
delayPressEnd: number,
delayPressStart: number,
pressRetentionOffset: {
@@ -42,8 +39,6 @@ type PressProps = {|
preventContextMenu: boolean,
preventDefault: boolean,
stopPropagation: boolean,
enableLongPress: boolean,
longPressShouldCancelPress: () => boolean,
|};
type PressState = {
@@ -54,10 +49,8 @@ type PressState = {
addedRootEvents: boolean,
isActivePressed: boolean,
isActivePressStart: boolean,
isLongPressed: boolean,
isPressed: boolean,
isPressWithinResponderRegion: boolean,
longPressTimeout: null | number,
pointerType: PointerType,
pressTarget: null | Element | Document,
pressEndTimeout: null | number,
@@ -86,8 +79,6 @@ type PressEventType =
| 'pressstart'
| 'pressend'
| 'presschange'
| 'longpress'
| 'longpresschange'
| 'contextmenu';
type PressEvent = {|
@@ -117,7 +108,6 @@ const isMac =
: false;
const DEFAULT_PRESS_END_DELAY_MS = 0;
const DEFAULT_PRESS_START_DELAY_MS = 0;
const DEFAULT_LONG_PRESS_DELAY_MS = 500;
const DEFAULT_PRESS_RETENTION_OFFSET = {
bottom: 20,
top: 20,
@@ -250,14 +240,6 @@ function dispatchPressChangeEvent(
context.dispatchEvent('onPressChange', bool, DiscreteEvent);
}
function dispatchLongPressChangeEvent(
context: ReactDOMResponderContext,
state: PressState,
): void {
const bool = state.isLongPressed;
context.dispatchEvent('onLongPressChange', bool, DiscreteEvent);
}
function activate(event: ReactDOMResponderEvent, context, props, state) {
const nativeEvent: any = event.nativeEvent;
const {clientX: x, clientY: y} = state.touchEvent || nativeEvent;
@@ -281,15 +263,10 @@ function activate(event: ReactDOMResponderEvent, context, props, state) {
}
function deactivate(event: ?ReactDOMResponderEvent, context, props, state) {
const wasLongPressed = state.isLongPressed;
state.isActivePressed = false;
state.isLongPressed = false;
dispatchEvent('onPressEnd', event, context, state, 'pressend', DiscreteEvent);
dispatchPressChangeEvent(context, state);
if (wasLongPressed && props.enableLongPress) {
dispatchLongPressChangeEvent(context, state);
}
}
function dispatchPressStartEvents(
@@ -308,27 +285,6 @@ function dispatchPressStartEvents(
const dispatch = () => {
state.isActivePressStart = true;
activate(event, context, props, state);
if (!state.isLongPressed && props.enableLongPress) {
const delayLongPress = calculateDelayMS(
props.delayLongPress,
10,
DEFAULT_LONG_PRESS_DELAY_MS,
);
state.longPressTimeout = context.setTimeout(() => {
state.isLongPressed = true;
state.longPressTimeout = null;
dispatchEvent(
'onLongPress',
event,
context,
state,
'longpress',
DiscreteEvent,
);
dispatchLongPressChangeEvent(context, state);
}, delayLongPress);
}
};
if (!state.isActivePressStart) {
@@ -360,11 +316,6 @@ function dispatchPressEndEvents(
state.isActivePressStart = false;
state.isPressed = false;
if (state.longPressTimeout !== null) {
context.clearTimeout(state.longPressTimeout);
state.longPressTimeout = null;
}
if (!wasActivePressStart && state.pressStartTimeout !== null) {
context.clearTimeout(state.pressStartTimeout);
state.pressStartTimeout = null;
@@ -596,10 +547,8 @@ const pressResponderImpl = {
addedRootEvents: false,
isActivePressed: false,
isActivePressStart: false,
isLongPressed: false,
isPressed: false,
isPressWithinResponderRegion: true,
longPressTimeout: null,
pointerType: '',
pressEndTimeout: null,
pressStartTimeout: null,
@@ -823,19 +772,6 @@ const pressResponderImpl = {
'pressmove',
UserBlockingEvent,
);
if (
state.activationPosition != null &&
state.longPressTimeout != null
) {
const deltaX = state.activationPosition.x - nativeEvent.clientX;
const deltaY = state.activationPosition.y - nativeEvent.clientY;
if (
Math.hypot(deltaX, deltaY) > 10 &&
state.longPressTimeout != null
) {
context.clearTimeout(state.longPressTimeout);
}
}
} else {
dispatchPressStartEvents(event, context, props, state);
}
@@ -900,7 +836,6 @@ const pressResponderImpl = {
}
}
const wasLongPressed = state.isLongPressed;
const pressTarget = state.pressTarget;
dispatchPressEndEvents(event, context, props, state);
@@ -928,23 +863,14 @@ const pressResponderImpl = {
}
}
if (state.isPressWithinResponderRegion && button !== 1) {
if (
!(
wasLongPressed &&
props.enableLongPress &&
props.longPressShouldCancelPress &&
props.longPressShouldCancelPress()
)
) {
dispatchEvent(
'onPress',
event,
context,
state,
'press',
DiscreteEvent,
);
}
dispatchEvent(
'onPress',
event,
context,
state,
'press',
DiscreteEvent,
);
}
}
state.touchEvent = null;

View File

@@ -928,258 +928,6 @@ describe('Event responder: Press', () => {
// });
});
describe('onLongPress', () => {
let onLongPress, ref;
beforeEach(() => {
onLongPress = jest.fn();
ref = React.createRef();
const Component = () => {
usePressListener({
onLongPress,
});
return (
<div
ref={ref}
responders={<PressResponder enableLongPress={true} />}
/>
);
};
ReactDOM.render(<Component />, container);
});
it('is called if "pointerdown" lasts default delay', () => {
ref.current.dispatchEvent(
createEvent('pointerdown', {pointerType: 'pen'}),
);
ref.current.dispatchEvent(
createTouchEvent('touchstart', 0, {
target: ref.current,
}),
);
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
expect(onLongPress).not.toBeCalled();
jest.advanceTimersByTime(1);
expect(onLongPress).toHaveBeenCalledTimes(1);
expect(onLongPress).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'pen', type: 'longpress'}),
);
});
it('is not called if "pointerup" is dispatched before delay', () => {
ref.current.dispatchEvent(createEvent('pointerdown'));
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
ref.current.dispatchEvent(createEvent('pointerup'));
jest.advanceTimersByTime(1);
expect(onLongPress).not.toBeCalled();
});
it('is called if valid "keydown" lasts default delay', () => {
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
expect(onLongPress).not.toBeCalled();
jest.advanceTimersByTime(1);
expect(onLongPress).toHaveBeenCalledTimes(1);
expect(onLongPress).toHaveBeenCalledWith(
expect.objectContaining({pointerType: 'keyboard', type: 'longpress'}),
);
});
it('is not called if valid "keyup" is dispatched before delay', () => {
ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Enter'}));
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
ref.current.dispatchEvent(createKeyboardEvent('keyup', {key: 'Enter'}));
jest.advanceTimersByTime(1);
expect(onLongPress).not.toBeCalled();
});
it('is not called when a large enough move occurs before delay', () => {
ref.current.getBoundingClientRect = () => ({
top: 0,
left: 0,
bottom: 100,
right: 100,
});
ref.current.dispatchEvent(
createEvent('pointerdown', {clientX: 10, clientY: 10}),
);
ref.current.dispatchEvent(
createEvent('pointermove', {clientX: 50, clientY: 50}),
);
jest.runAllTimers();
expect(onLongPress).not.toBeCalled();
});
describe('delayLongPress', () => {
it('can be configured', () => {
const Component = () => {
usePressListener({
onLongPress,
});
return (
<div
ref={ref}
responders={
<PressResponder delayLongPress={2000} enableLongPress={true} />
}
/>
);
};
ReactDOM.render(<Component />, container);
ref.current.dispatchEvent(createEvent('pointerdown'));
jest.advanceTimersByTime(1999);
expect(onLongPress).not.toBeCalled();
jest.advanceTimersByTime(1);
expect(onLongPress).toHaveBeenCalledTimes(1);
});
it('uses 10ms minimum delay length', () => {
const Component = () => {
usePressListener({
onLongPress,
});
return (
<div
ref={ref}
responders={
<PressResponder delayLongPress={0} enableLongPress={true} />
}
/>
);
};
ReactDOM.render(<Component />, container);
ref.current.dispatchEvent(createEvent('pointerdown'));
jest.advanceTimersByTime(9);
expect(onLongPress).not.toBeCalled();
jest.advanceTimersByTime(1);
expect(onLongPress).toHaveBeenCalledTimes(1);
});
it('compounds with "delayPressStart"', () => {
const delayPressStart = 100;
const Component = () => {
usePressListener({
onLongPress,
});
return (
<div
ref={ref}
responders={
<PressResponder
delayPressStart={delayPressStart}
enableLongPress={true}
/>
}
/>
);
};
ReactDOM.render(<Component />, container);
ref.current.dispatchEvent(createEvent('pointerdown'));
jest.advanceTimersByTime(
delayPressStart + DEFAULT_LONG_PRESS_DELAY - 1,
);
expect(onLongPress).not.toBeCalled();
jest.advanceTimersByTime(1);
expect(onLongPress).toHaveBeenCalledTimes(1);
});
});
});
describe('onLongPressChange', () => {
it('is called when long press state changes', () => {
const onLongPressChange = jest.fn();
const ref = React.createRef();
const Component = () => {
usePressListener({
onLongPressChange,
});
return (
<div
ref={ref}
responders={<PressResponder enableLongPress={true} />}
/>
);
};
ReactDOM.render(<Component />, container);
ref.current.dispatchEvent(createEvent('pointerdown'));
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
expect(onLongPressChange).toHaveBeenCalledTimes(1);
expect(onLongPressChange).toHaveBeenCalledWith(true);
ref.current.dispatchEvent(createEvent('pointerup'));
expect(onLongPressChange).toHaveBeenCalledTimes(2);
expect(onLongPressChange).toHaveBeenCalledWith(false);
});
it('is called after delayed onPressEnd', () => {
const onLongPressChange = jest.fn();
const ref = React.createRef();
const Component = () => {
usePressListener({
onLongPressChange,
});
return (
<div
ref={ref}
responders={
<PressResponder delayPressEnd={500} enableLongPress={true} />
}
/>
);
};
ReactDOM.render(<Component />, container);
ref.current.dispatchEvent(createEvent('pointerdown'));
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
expect(onLongPressChange).toHaveBeenCalledTimes(1);
expect(onLongPressChange).toHaveBeenCalledWith(true);
ref.current.dispatchEvent(createEvent('pointerup'));
jest.advanceTimersByTime(499);
expect(onLongPressChange).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(1);
expect(onLongPressChange).toHaveBeenCalledTimes(2);
expect(onLongPressChange).toHaveBeenCalledWith(false);
});
});
describe('longPressShouldCancelPress', () => {
it('if true it cancels "onPress"', () => {
const onPress = jest.fn();
const onPressChange = jest.fn();
const ref = React.createRef();
const Component = () => {
usePressListener({
onLongPress: () => {},
onPressChange,
onPress,
});
return (
<div
ref={ref}
responders={
<PressResponder
longPressShouldCancelPress={() => true}
enableLongPress={true}
/>
}
/>
);
};
ReactDOM.render(<Component />, container);
// NOTE: onPressChange behavior should not be affected
ref.current.dispatchEvent(createEvent('pointerdown'));
expect(onPressChange).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
ref.current.dispatchEvent(createEvent('pointerup'));
expect(onPress).not.toBeCalled();
expect(onPressChange).toHaveBeenCalledTimes(2);
});
});
describe('onPressMove', () => {
it('is called after "pointermove"', () => {
const onPressMove = jest.fn();
@@ -1621,7 +1369,6 @@ describe('Event responder: Press', () => {
const Component = () => {
usePressListener({
onLongPress: createEventHandler('onLongPress'),
onPress: createEventHandler('onPress'),
onPressChange: createEventHandler('onPressChange'),
onPressMove: createEventHandler('onPressMove'),
@@ -1632,11 +1379,7 @@ describe('Event responder: Press', () => {
<div
ref={ref}
responders={
<PressResponder
delayPressStart={500}
delayPressEnd={500}
enableLongPress={true}
/>
<PressResponder delayPressStart={500} delayPressEnd={500} />
}
/>
);
@@ -2243,7 +1986,6 @@ describe('Event responder: Press', () => {
const Component = () => {
usePressListener({
onLongPress: createEventHandler('onLongPress'),
onPress: createEventHandler('onPress'),
onPressChange: createEventHandler('onPressChange'),
onPressMove: createEventHandler('onPressMove'),
@@ -2254,11 +1996,7 @@ describe('Event responder: Press', () => {
<div
ref={ref}
responders={
<PressResponder
delayPressStart={500}
delayPressEnd={500}
enableLongPress={true}
/>
<PressResponder delayPressStart={500} delayPressEnd={500} />
}
/>
);
@@ -2378,8 +2116,6 @@ describe('Event responder: Press', () => {
const Component = () => {
usePressListener({
onLongPress: createEventHandler('onLongPress'),
onLongPressChange: createEventHandler('onLongPressChange'),
onPress: createEventHandler('onPress'),
onPressChange: createEventHandler('onPressChange'),
onPressMove: createEventHandler('onPressMove'),
@@ -2390,11 +2126,7 @@ describe('Event responder: Press', () => {
<div
ref={ref}
responders={
<PressResponder
delayPressStart={250}
delayPressEnd={250}
enableLongPress={true}
/>
<PressResponder delayPressStart={250} delayPressEnd={250} />
}
/>
);
@@ -2443,12 +2175,9 @@ describe('Event responder: Press', () => {
expect(events).toEqual([
'onPressStart',
'onPressChange',
'onLongPress',
'onLongPressChange',
'onPress',
'onPressEnd',
'onPressChange',
'onLongPressChange',
]);
});
});
@@ -2557,40 +2286,6 @@ describe('Event responder: Press', () => {
expect(fn).toHaveBeenCalledTimes(1);
});
it('for onLongPress', () => {
const ref = React.createRef();
const fn = jest.fn();
const Inner = () => {
usePressListener({
onLongPress: fn,
});
return (
<div
ref={ref}
responders={<PressResponder enableLongPress={true} />}
/>
);
};
const Outer = () => {
usePressListener({
onLongPress: fn,
});
return (
<div responders={<PressResponder enableLongPress={true} />}>
<Inner />
</div>
);
};
ReactDOM.render(<Outer />, container);
ref.current.dispatchEvent(createEvent('pointerdown'));
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY);
ref.current.dispatchEvent(createEvent('pointerup'));
expect(fn).toHaveBeenCalledTimes(1);
});
it('for onPressStart/onPressEnd', () => {
const ref = React.createRef();
const fn = jest.fn();
@@ -2863,22 +2558,14 @@ describe('Event responder: Press', () => {
describe('responder cancellation', () => {
it('ends on "pointercancel", "touchcancel", "scroll", and "dragstart"', () => {
const onLongPress = jest.fn();
const onPressEnd = jest.fn();
const ref = React.createRef();
const Component = () => {
usePressListener({
onLongPress,
onPressEnd,
});
return (
<a
href="#"
ref={ref}
responders={<PressResponder enableLongPress={true} />}
/>
);
return <a href="#" ref={ref} responders={<PressResponder />} />;
};
ReactDOM.render(<Component />, container);
@@ -2890,8 +2577,6 @@ describe('Event responder: Press', () => {
);
ref.current.dispatchEvent(createEvent('scroll'));
expect(onPressEnd).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(onLongPress).not.toBeCalled();
onPressEnd.mockReset();
@@ -2903,9 +2588,6 @@ describe('Event responder: Press', () => {
);
ref.current.dispatchEvent(createEvent('scroll'));
expect(onPressEnd).toHaveBeenCalledTimes(0);
jest.runAllTimers();
onLongPress.mockReset();
// When pointer events are supported
ref.current.dispatchEvent(
@@ -2919,10 +2601,7 @@ describe('Event responder: Press', () => {
}),
);
expect(onPressEnd).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(onLongPress).not.toBeCalled();
onLongPress.mockReset();
onPressEnd.mockReset();
// Touch fallback
@@ -2937,18 +2616,13 @@ describe('Event responder: Press', () => {
}),
);
expect(onPressEnd).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(onLongPress).not.toBeCalled();
onLongPress.mockReset();
onPressEnd.mockReset();
// Mouse fallback
ref.current.dispatchEvent(createEvent('mousedown'));
ref.current.dispatchEvent(createEvent('dragstart'));
expect(onPressEnd).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(onLongPress).not.toBeCalled();
});
});
@@ -3059,15 +2733,9 @@ describe('Event responder: Press', () => {
onPressStart: logEvent,
onPressEnd: logEvent,
onPressMove: logEvent,
onLongPress: logEvent,
onPress: logEvent,
});
return (
<button
ref={ref}
responders={<PressResponder enableLongPress={true} />}
/>
);
return <button ref={ref} responders={<PressResponder />} />;
};
ReactDOM.render(<Component />, container);
@@ -3137,18 +2805,6 @@ describe('Event responder: Press', () => {
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,
@@ -3158,7 +2814,7 @@ describe('Event responder: Press', () => {
clientX: 31,
clientY: 32,
target: ref.current,
timeStamp: timeStamps[2],
timeStamp: timeStamps[1],
type: 'pressmove',
},
{
@@ -3170,7 +2826,7 @@ describe('Event responder: Press', () => {
clientX: 32,
clientY: 33,
target: ref.current,
timeStamp: timeStamps[3],
timeStamp: timeStamps[2],
type: 'pressend',
},
{
@@ -3194,7 +2850,7 @@ describe('Event responder: Press', () => {
clientX: 33,
clientY: 34,
target: ref.current,
timeStamp: timeStamps[5],
timeStamp: timeStamps[4],
type: 'pressstart',
},
]);