Pressability: Explicitly Commit Configurations

Summary:
Refactors `Pressability` so that updates to the configuration are now explicitly committed using `configure()`.

Previously, the configuration was updated implicitly because `Pressability` accepted a series of functions whose closures capture values (e.g. `this.props`). Although these changes typically happen when component instances are "atomically" updated by React, it is not a guarantee. For example, arbitrary instance variables could be used to configure `Pressability`, and they could be muted at any time.

This change makes updates to the configuration of `Pressability` more predictable.

Changelog:
[Internal]

Reviewed By: TheSavior

Differential Revision: D18742620

fbshipit-source-id: d2e96dd1e3643289daab2177199a29f80d17b0bc
This commit is contained in:
Tim Yung authored and Facebook Github Bot committed 2019-12-15 22:07:47 -08:00
1 parent ebdce9b3b0
commit 9b81232ed2
7 files changed
+284 -301

No files matched your search

@@ -10,7 +10,9 @@
'use strict';
import Pressability from '../../Pressability/Pressability.js';
import Pressability, {
type PressabilityConfig,
} from '../../Pressability/Pressability.js';
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug.js';
import type {ViewStyleProp} from '../../StyleSheet/StyleSheet.js';
import TVTouchable from './TVTouchable.js';
@@ -39,21 +41,20 @@ class TouchableBounce extends React.Component<Props, State> {
_tvTouchable: ?TVTouchable;
state: State = {
pressability: new Pressability({
getHitSlop: () => this.props.hitSlop,
getLongPressDelayMS: () => {
if (this.props.delayLongPress != null) {
const maybeNumber = this.props.delayLongPress;
if (typeof maybeNumber === 'number') {
return maybeNumber;
}
}
return 500;
},
getPressDelayMS: () => this.props.delayPressIn,
getPressOutDelayMS: () => this.props.delayPressOut,
getPressRectOffset: () => this.props.pressRetentionOffset,
getTouchSoundDisabled: () => this.props.touchSoundDisabled,
pressability: new Pressability(this._createPressabilityConfig()),
scale: new Animated.Value(1),
};
_createPressabilityConfig(): PressabilityConfig {
return {
cancelable: !this.props.rejectResponderTermination,
disabled: this.props.disabled,
hitSlop: this.props.hitSlop,
delayLongPress: this.props.delayLongPress,
delayPressIn: this.props.delayPressIn,
delayPressOut: this.props.delayPressOut,
pressRectOffset: this.props.pressRetentionOffset,
android_disableSound: this.props.touchSoundDisabled,
onBlur: event => {
if (Platform.isTV) {
this._bounceTo(1, 0.4, 0);
@@ -115,12 +116,8 @@ class TouchableBounce extends React.Component<Props, State> {
this.props.onPressOut(event);
}
},
onResponderTerminationRequest: () =>
!this.props.rejectResponderTermination,
onStartShouldSetResponder: () => !this.props.disabled,
}),
scale: new Animated.Value(1),
};
};
}
_bounceTo(
toValue: number,
@@ -201,6 +198,10 @@ class TouchableBounce extends React.Component<Props, State> {
}
}
componentDidUpdate(prevProps: Props, prevState: State) {
this.state.pressability.configure(this._createPressabilityConfig());
}
componentWillUnmount(): void {
if (Platform.isTV) {
if (this._tvTouchable != null) {
@@ -10,7 +10,9 @@
'use strict';
import Pressability from '../../Pressability/Pressability.js';
import Pressability, {
type PressabilityConfig,
} from '../../Pressability/Pressability.js';
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug.js';
import StyleSheet, {type ViewStyleProp} from '../../StyleSheet/StyleSheet.js';
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes.js';
@@ -159,21 +161,21 @@ class TouchableHighlight extends React.Component<Props, State> {
_tvTouchable: ?TVTouchable;
state: State = {
pressability: new Pressability({
getHitSlop: () => this.props.hitSlop,
getLongPressDelayMS: () => {
if (this.props.delayLongPress != null) {
const maybeNumber = this.props.delayLongPress;
if (typeof maybeNumber === 'number') {
return maybeNumber;
}
}
return 500;
},
getPressDelayMS: () => this.props.delayPressIn,
getPressOutDelayMS: () => this.props.delayPressOut,
getPressRectOffset: () => this.props.pressRetentionOffset,
getTouchSoundDisabled: () => this.props.touchSoundDisabled,
pressability: new Pressability(this._createPressabilityConfig()),
extraStyles:
this.props.testOnly_pressed === true ? this._createExtraStyles() : null,
};
_createPressabilityConfig(): PressabilityConfig {
return {
cancelable: !this.props.rejectResponderTermination,
disabled: this.props.disabled,
hitSlop: this.props.hitSlop,
delayLongPress: this.props.delayLongPress,
delayPressIn: this.props.delayPressIn,
delayPressOut: this.props.delayPressOut,
pressRectOffset: this.props.pressRetentionOffset,
android_disableSound: this.props.touchSoundDisabled,
onBlur: event => {
if (Platform.isTV) {
this._hideUnderlay();
@@ -227,13 +229,8 @@ class TouchableHighlight extends React.Component<Props, State> {
this.props.onPressOut(event);
}
},
onResponderTerminationRequest: () =>
!this.props.rejectResponderTermination,
onStartShouldSetResponder: () => !this.props.disabled,
}),
extraStyles:
this.props.testOnly_pressed === true ? this._createExtraStyles() : null,
};
};
}
_createExtraStyles(): ExtraStyles {
return {
@@ -363,6 +360,10 @@ class TouchableHighlight extends React.Component<Props, State> {
}
}
componentDidUpdate(prevProps: Props, prevState: State) {
this.state.pressability.configure(this._createPressabilityConfig());
}
componentWillUnmount(): void {
this._isMounted = false;
if (this._hideTimeout != null) {
@@ -10,7 +10,9 @@
'use strict';
import Pressability from '../../Pressability/Pressability.js';
import Pressability, {
type PressabilityConfig,
} from '../../Pressability/Pressability.js';
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug.js';
import TVTouchable from './TVTouchable.js';
import typeof TouchableWithoutFeedback from './TouchableWithoutFeedback.js';
@@ -144,31 +146,21 @@ class TouchableNativeFeedback extends React.Component<Props, State> {
_tvTouchable: ?TVTouchable;
state: State = {
pressability: new Pressability({
getHitSlop: () => this.props.hitSlop,
getLongPressDelayMS: () => {
if (this.props.delayLongPress != null) {
const maybeNumber = this.props.delayLongPress;
if (typeof maybeNumber === 'number') {
return maybeNumber;
}
}
return 500;
},
getPressDelayMS: () => this.props.delayPressIn,
getPressOutDelayMS: () => this.props.delayPressOut,
getPressRectOffset: () => this.props.pressRetentionOffset,
getTouchSoundDisabled: () => this.props.touchSoundDisabled,
onLongPress: event => {
if (this.props.onLongPress != null) {
this.props.onLongPress(event);
}
},
onPress: event => {
if (this.props.onPress != null) {
this.props.onPress(event);
}
},
pressability: new Pressability(this._createPressabilityConfig()),
};
_createPressabilityConfig(): PressabilityConfig {
return {
cancelable: !this.props.rejectResponderTermination,
disabled: this.props.disabled,
hitSlop: this.props.hitSlop,
delayLongPress: this.props.delayLongPress,
delayPressIn: this.props.delayPressIn,
delayPressOut: this.props.delayPressOut,
pressRectOffset: this.props.pressRetentionOffset,
android_disableSound: this.props.touchSoundDisabled,
onLongPress: this.props.onLongPress,
onPress: this.props.onPress,
onPressIn: event => {
if (Platform.OS === 'android') {
this._dispatchPressedStateChange(true);
@@ -191,11 +183,8 @@ class TouchableNativeFeedback extends React.Component<Props, State> {
this.props.onPressOut(event);
}
},
onResponderTerminationRequest: () =>
!this.props.rejectResponderTermination,
onStartShouldSetResponder: () => !this.props.disabled,
}),
};
};
}
_dispatchPressedStateChange(pressed: boolean): void {
if (Platform.OS === 'android') {
@@ -312,6 +301,10 @@ class TouchableNativeFeedback extends React.Component<Props, State> {
}
}
componentDidUpdate(prevProps: Props, prevState: State) {
this.state.pressability.configure(this._createPressabilityConfig());
}
componentWillUnmount(): void {
if (Platform.isTV) {
if (this._tvTouchable != null) {
@@ -10,7 +10,9 @@
'use strict';
import Pressability from '../../Pressability/Pressability.js';
import Pressability, {
type PressabilityConfig,
} from '../../Pressability/Pressability.js';
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug.js';
import TVTouchable from './TVTouchable.js';
import typeof TouchableWithoutFeedback from './TouchableWithoutFeedback.js';
@@ -134,20 +136,18 @@ class TouchableOpacity extends React.Component<Props, State> {
state: State = {
anim: new Animated.Value(this._getChildStyleOpacityWithDefault()),
pressability: new Pressability({
getHitSlop: () => this.props.hitSlop,
getLongPressDelayMS: () => {
if (this.props.delayLongPress != null) {
const maybeNumber = this.props.delayLongPress;
if (typeof maybeNumber === 'number') {
return maybeNumber;
}
}
return 500;
},
getPressDelayMS: () => this.props.delayPressIn,
getPressOutDelayMS: () => this.props.delayPressOut,
getPressRectOffset: () => this.props.pressRetentionOffset,
pressability: new Pressability(this._createPressabilityConfig()),
};
_createPressabilityConfig(): PressabilityConfig {
return {
cancelable: !this.props.rejectResponderTermination,
disabled: this.props.disabled,
hitSlop: this.props.hitSlop,
delayLongPress: this.props.delayLongPress,
delayPressIn: this.props.delayPressIn,
delayPressOut: this.props.delayPressOut,
pressRectOffset: this.props.pressRetentionOffset,
onBlur: event => {
if (Platform.isTV) {
this._opacityInactive(250);
@@ -164,16 +164,8 @@ class TouchableOpacity extends React.Component<Props, State> {
this.props.onFocus(event);
}
},
onLongPress: event => {
if (this.props.onLongPress != null) {
this.props.onLongPress(event);
}
},
onPress: event => {
if (this.props.onPress != null) {
this.props.onPress(event);
}
},
onLongPress: this.props.onLongPress,
onPress: this.props.onPress,
onPressIn: event => {
this._opacityActive(
event.dispatchConfig.registrationName === 'onResponderGrant'
@@ -190,11 +182,8 @@ class TouchableOpacity extends React.Component<Props, State> {
this.props.onPressOut(event);
}
},
onResponderTerminationRequest: () =>
!this.props.rejectResponderTermination,
onStartShouldSetResponder: () => !this.props.disabled,
}),
};
};
}
/**
* Animate the touchable to a new opacity.
@@ -292,6 +281,7 @@ class TouchableOpacity extends React.Component<Props, State> {
}
componentDidUpdate(prevProps: Props, prevState: State) {
this.state.pressability.configure(this._createPressabilityConfig());
if (this.props.disabled !== prevProps.disabled) {
this._opacityInactive(250);
}
@@ -10,7 +10,9 @@
'use strict';
import Pressability from '../../Pressability/Pressability.js';
import Pressability, {
type PressabilityConfig,
} from '../../Pressability/Pressability.js';
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug.js';
import TVTouchable from './TVTouchable.js';
import type {
@@ -95,55 +97,7 @@ class TouchableWithoutFeedback extends React.Component<Props, State> {
_tvTouchable: ?TVTouchable;
state: State = {
pressability: new Pressability({
getHitSlop: () => this.props.hitSlop,
getLongPressDelayMS: () => {
if (this.props.delayLongPress != null) {
const maybeNumber = this.props.delayLongPress;
if (typeof maybeNumber === 'number') {
return maybeNumber;
}
}
return 500;
},
getPressDelayMS: () => this.props.delayPressIn,
getPressOutDelayMS: () => this.props.delayPressOut,
getPressRectOffset: () => this.props.pressRetentionOffset,
getTouchSoundDisabled: () => this.props.touchSoundDisabled,
onBlur: event => {
if (this.props.onBlur != null) {
this.props.onBlur(event);
}
},
onFocus: event => {
if (this.props.onFocus != null) {
this.props.onFocus(event);
}
},
onLongPress: event => {
if (this.props.onLongPress != null) {
this.props.onLongPress(event);
}
},
onPress: event => {
if (this.props.onPress != null) {
this.props.onPress(event);
}
},
onPressIn: event => {
if (this.props.onPressIn != null) {
this.props.onPressIn(event);
}
},
onPressOut: event => {
if (this.props.onPressOut != null) {
this.props.onPressOut(event);
}
},
onResponderTerminationRequest: () =>
!this.props.rejectResponderTermination,
onStartShouldSetResponder: () => !this.props.disabled,
}),
pressability: new Pressability(createPressabilityConfig(this.props)),
};
render(): React.Node {
@@ -203,6 +157,10 @@ class TouchableWithoutFeedback extends React.Component<Props, State> {
}
}
componentDidUpdate(): void {
this.state.pressability.configure(createPressabilityConfig(this.props));
}
componentWillUnmount(): void {
if (Platform.isTV) {
if (this._tvTouchable != null) {
@@ -213,4 +171,23 @@ class TouchableWithoutFeedback extends React.Component<Props, State> {
}
}
function createPressabilityConfig(props: Props): PressabilityConfig {
return {
cancelable: !props.rejectResponderTermination,
disabled: props.disabled,
hitSlop: props.hitSlop,
delayLongPress: props.delayLongPress,
delayPressIn: props.delayPressIn,
delayPressOut: props.delayPressOut,
pressRectOffset: props.pressRetentionOffset,
android_disableSound: props.touchSoundDisabled,
onBlur: props.onBlur,
onFocus: props.onFocus,
onLongPress: props.onLongPress,
onPress: props.onPress,
onPressIn: props.onPressIn,
onPressOut: props.onPressOut,
};
}
module.exports = TouchableWithoutFeedback;
+131 -115
View File
@@ -27,107 +27,124 @@ import * as React from 'react';
export type PressabilityConfig = $ReadOnly<{|
/**
* Returns the amount to extend the `VisualRect` by to create `HitRect`.
* Whether a press gesture can be interrupted by a parent gesture such as a
* scroll event. Defaults to true.
*/
getHitSlop?: ?() => ?EdgeInsetsProp,
cancelable?: ?boolean,
/**
* Returns the duration to wait after hover in before activation.
* Whether to disable initialization of the press gesture.
*/
getHoverInDelayMS?: ?() => ?number,
disabled?: ?boolean,
/**
* Returns the duration to wait after hover out before deactivation.
* Amount to extend the `VisualRect` by to create `HitRect`.
*/
getHoverOutDelayMS?: ?() => ?number,
hitSlop?: ?EdgeInsetsProp,
/**
* Returns the duration (in addition to the value from `getPressDelayMS`)
* after which a press gesture becomes a long press gesture.
* Amount to extend the `HitRect` by to create `PressRect`.
*/
getLongPressDelayMS?: ?() => ?number,
pressRectOffset?: ?EdgeInsetsProp,
/**
* Returns the duration to wait after press down before activation.
*/
getPressDelayMS?: ?() => ?number,
/**
* Returns the duration to wait after letting up before deactivation.
*/
getPressOutDelayMS?: ?() => ?number,
/**
* Returns the amount to extend the `HitRect` by to create `PressRect`.
*/
getPressRectOffset?: ?() => ?EdgeInsetsProp,
/**
* Returns true to disable playing system sound on touch (Android Only)
* Whether to disable the systemm sound when `onPress` fires on Android.
**/
getTouchSoundDisabled?: ?() => ?boolean,
android_disableSound?: ?boolean,
/**
* Duration to wait after hover in before calling `onHoverIn`.
*/
delayHoverIn?: ?number,
/**
* Duration to wait after hover out before calling `onHoverOut`.
*/
delayHoverOut?: ?number,
/**
* Duration (in addition to `delayPressIn`) after which a press gesture is
* considered a long press gesture. Defaults to 500 (milliseconds).
*/
delayLongPress?: ?number,
/**
* Duration to wait after press down before calling `onPressIn`.
*/
delayPressIn?: ?number,
/**
* Duration to wait after letting up before calling `onPressOut`.
*/
delayPressOut?: ?number,
/**
* Called after the element loses focus.
*/
onBlur?: ?(event: BlurEvent) => void,
onBlur?: ?(event: BlurEvent) => mixed,
/**
* Called after the element is focused.
*/
onFocus?: ?(event: FocusEvent) => void,
onFocus?: ?(event: FocusEvent) => mixed,
/**
* Called when the hover is activated to provide visual feedback.
*/
onHoverIn?: ?(event: MouseEvent) => void,
onHoverIn?: ?(event: MouseEvent) => mixed,
/**
* Called when the hover is deactivated to undo visual feedback.
*/
onHoverOut?: ?(event: MouseEvent) => void,
onHoverOut?: ?(event: MouseEvent) => mixed,
/**
* Called when a long press gesture has been triggered.
*/
onLongPress?: ?(event: PressEvent) => void,
onLongPress?: ?(event: PressEvent) => mixed,
/**
* Called when a press gestute has been triggered.
*/
onPress?: ?(event: PressEvent) => mixed,
/**
* Called when the press is activated to provide visual feedback.
*/
onPressIn?: ?(event: PressEvent) => mixed,
/**
* Called when the press location moves. (This should rarely be used.)
*/
onPressMove?: ?(event: PressEvent) => mixed,
/**
* Called when the press is deactivated to undo visual feedback.
*/
onPressOut?: ?(event: PressEvent) => mixed,
/**
* Returns whether a long press gesture should cancel the press gesture.
* Defaults to true.
*/
onLongPressShouldCancelPress?: ?() => boolean,
/**
* Called when a press gestute has been triggered.
*/
onPress?: ?(event: PressEvent) => void,
/**
* Called when the press is activated to provide visual feedback.
*/
onPressIn?: ?(event: PressEvent) => void,
/**
* Called when the press location moves. (This should rarely be used.)
*/
onPressMove?: ?(event: PressEvent) => void,
/**
* Called when the press is deactivated to undo visual feedback.
*/
onPressOut?: ?(event: PressEvent) => void,
onLongPressShouldCancelPress_DEPRECATED?: ?() => boolean,
/**
* If `cancelable` is set, this will be ignored.
*
* Returns whether to yield to a lock termination request (e.g. if a native
* scroll gesture attempts to steal the responder lock).
*/
onResponderTerminationRequest?: ?() => boolean,
onResponderTerminationRequest_DEPRECATED?: ?() => boolean,
/**
* If `disabled` is set, this will be ignored.
*
* Returns whether to start a press gesture.
*
* @deprecated
*/
onStartShouldSetResponder?: ?() => boolean,
onStartShouldSetResponder_DEPRECATED?: ?() => boolean,
|}>;
type EventHandlers = $ReadOnly<{|
@@ -315,7 +332,7 @@ const DEFAULT_PRESS_RECT_OFFSETS = {
*
* ┌────────────────────────┐
* │ ┌──────────────────┐ │ - Presses start anywhere within `HitRect`, which
* │ │ ┌────────────┐ │ │ is expanded via the prop `getHitSlop`.
* │ │ ┌────────────┐ │ │ is expanded via the prop `hitSlop`.
* │ │ │ VisualRect │ │ │
* │ │ └────────────┘ │ │ - When pressed down for sufficient amount of time
* │ │ HitRect │ │ before letting up, `VisualRect` activates for
@@ -323,7 +340,7 @@ const DEFAULT_PRESS_RECT_OFFSETS = {
* │ PressRect o │
* └────────────────────│───┘
* Out Region └────── `PressRect`, which is expanded via the prop
* `getPressRectOffset`, allows presses to move
* `pressRectOffset`, allows presses to move
* beyond `HitRect` while maintaining activation
* and being eligible for a "press".
*
@@ -379,6 +396,10 @@ export default class Pressability {
_touchState: TouchState = 'NOT_RESPONDER';
constructor(config: PressabilityConfig) {
this.configure(config);
}
configure(config: PressabilityConfig): void {
this._config = config;
}
@@ -420,11 +441,15 @@ export default class Pressability {
};
const responderEventHandlers = {
onStartShouldSetResponder: () => {
const {onStartShouldSetResponder} = this._config;
return onStartShouldSetResponder == null
? true
: onStartShouldSetResponder();
onStartShouldSetResponder: (): boolean => {
const {disabled} = this._config;
if (disabled == null) {
const {onStartShouldSetResponder_DEPRECATED} = this._config;
return onStartShouldSetResponder_DEPRECATED == null
? true
: onStartShouldSetResponder_DEPRECATED();
}
return !disabled;
},
onResponderGrant: (event: PressEvent): void => {
@@ -436,29 +461,28 @@ export default class Pressability {
this._touchState = 'NOT_RESPONDER';
this._receiveSignal('RESPONDER_GRANT', event);
const {getLongPressDelayMS, getPressDelayMS} = this._config;
const pressDelay = getDelayMS(
getPressDelayMS,
const delayPressIn = normalizeDelay(
this._config.delayPressIn,
0,
DEFAULT_PRESS_DELAY_MS,
);
if (pressDelay > 0) {
if (delayPressIn > 0) {
this._pressDelayTimeout = setTimeout(() => {
this._receiveSignal('DELAY', event);
}, pressDelay);
}, delayPressIn);
} else {
this._receiveSignal('DELAY', event);
}
const longPressDelay = getDelayMS(
getLongPressDelayMS,
const delayLongPress = normalizeDelay(
this._config.delayLongPress,
10,
DEFAULT_LONG_PRESS_DELAY_MS,
);
this._longPressDelayTimeout = setTimeout(() => {
this._handleLongPress(event);
}, longPressDelay + pressDelay);
}, delayLongPress + delayPressIn);
},
onResponderMove: (event: PressEvent): void => {
@@ -504,13 +528,17 @@ export default class Pressability {
},
onResponderTerminationRequest: (): boolean => {
const {onResponderTerminationRequest} = this._config;
return onResponderTerminationRequest == null
? true
: onResponderTerminationRequest();
const {cancelable} = this._config;
if (cancelable == null) {
const {onResponderTerminationRequest_DEPRECATED} = this._config;
return onResponderTerminationRequest_DEPRECATED == null
? true
: onResponderTerminationRequest_DEPRECATED();
}
return cancelable;
},
onClick: (event: PressEvent) => {
onClick: (event: PressEvent): void => {
const {onPress} = this._config;
if (onPress != null) {
onPress(event);
@@ -526,13 +554,15 @@ export default class Pressability {
if (isHoverEnabled()) {
this._isHovered = true;
this._cancelHoverOutDelayTimeout();
const {onHoverIn, getHoverInDelayMS} = this._config;
const {onHoverIn} = this._config;
if (onHoverIn != null) {
const delay = getDelayMS(getHoverInDelayMS);
if (delay > 0) {
const delayHoverIn = normalizeDelay(
this._config.delayHoverIn,
);
if (delayHoverIn > 0) {
this._hoverInDelayTimeout = setTimeout(() => {
onHoverIn(event);
}, delay);
}, delayHoverIn);
} else {
onHoverIn(event);
}
@@ -544,13 +574,15 @@ export default class Pressability {
if (this._isHovered) {
this._isHovered = false;
this._cancelHoverInDelayTimeout();
const {onHoverOut, getHoverOutDelayMS} = this._config;
const {onHoverOut} = this._config;
if (onHoverOut != null) {
const delay = getDelayMS(getHoverOutDelayMS);
if (delay > 0) {
const delayHoverOut = normalizeDelay(
this._config.delayHoverOut,
);
if (delayHoverOut > 0) {
this._hoverInDelayTimeout = setTimeout(() => {
onHoverOut(event);
}, delay);
}, delayHoverOut);
} else {
onHoverOut(event);
}
@@ -633,7 +665,7 @@ export default class Pressability {
}
if (isPressInSignal(prevState) && signal === 'RESPONDER_RELEASE') {
const {onLongPress, onPress, getTouchSoundDisabled} = this._config;
const {onLongPress, onPress, android_disableSound} = this._config;
if (onPress != null) {
const isPressCanceledByLongPress =
onLongPress != null &&
@@ -645,10 +677,7 @@ export default class Pressability {
this._activate(event);
this._deactivate(event);
}
const isTouchSoundDisabled =
(getTouchSoundDisabled == null ? null : getTouchSoundDisabled()) ??
false;
if (Platform.OS === 'android' && !isTouchSoundDisabled) {
if (Platform.OS === 'android' && android_disableSound !== true) {
SoundManager.playTouchSound();
}
onPress(event);
@@ -672,13 +701,13 @@ export default class Pressability {
}
_deactivate(event: PressEvent): void {
const {onPressOut, getPressOutDelayMS} = this._config;
const {onPressOut} = this._config;
if (onPressOut != null) {
const delay = getDelayMS(getPressOutDelayMS);
if (delay > 0) {
const delayPressOut = normalizeDelay(this._config.delayPressOut);
if (delayPressOut > 0) {
this._pressOutDelayTimeout = setTimeout(() => {
onPressOut(event);
}, delay);
}, delayPressOut);
} else {
onPressOut(event);
}
@@ -718,14 +747,13 @@ export default class Pressability {
top: number,
|}>,
): boolean {
const {getHitSlop, getPressRectOffset} = this._config;
const {hitSlop, pressRectOffset} = this._config;
let regionBottom = responderRegion.bottom;
let regionLeft = responderRegion.left;
let regionRight = responderRegion.right;
let regionTop = responderRegion.top;
const hitSlop = getHitSlop == null ? null : getHitSlop();
if (hitSlop != null) {
if (hitSlop.bottom != null) {
regionBottom += hitSlop.bottom;
@@ -741,23 +769,11 @@ export default class Pressability {
}
}
const rectOffset = getPressRectOffset == null ? null : getPressRectOffset();
regionBottom +=
rectOffset == null || rectOffset.bottom == null
? DEFAULT_PRESS_RECT_OFFSETS.bottom
: rectOffset.bottom;
regionLeft -=
rectOffset == null || rectOffset.left == null
? DEFAULT_PRESS_RECT_OFFSETS.left
: rectOffset.left;
regionRight +=
rectOffset == null || rectOffset.right == null
? DEFAULT_PRESS_RECT_OFFSETS.right
: rectOffset.right;
regionTop -=
rectOffset == null || rectOffset.top == null
? DEFAULT_PRESS_RECT_OFFSETS.top
: rectOffset.top;
pressRectOffset?.bottom ?? DEFAULT_PRESS_RECT_OFFSETS.bottom;
regionLeft -= pressRectOffset?.left ?? DEFAULT_PRESS_RECT_OFFSETS.left;
regionRight += pressRectOffset?.right ?? DEFAULT_PRESS_RECT_OFFSETS.right;
regionTop -= pressRectOffset?.top ?? DEFAULT_PRESS_RECT_OFFSETS.top;
return (
touch.pageX > regionLeft &&
@@ -778,8 +794,8 @@ export default class Pressability {
_shouldLongPressCancelPress(): boolean {
return (
this._config.onLongPressShouldCancelPress == null ||
this._config.onLongPressShouldCancelPress()
this._config.onLongPressShouldCancelPress_DEPRECATED == null ||
this._config.onLongPressShouldCancelPress_DEPRECATED()
);
}
@@ -819,9 +835,9 @@ export default class Pressability {
}
}
const getDelayMS = (getDelay: ?() => ?number, min = 0, fallback = 0) => {
return Math.max(min, (getDelay == null ? null : getDelay()) ?? fallback);
};
function normalizeDelay(delay: ?number, min = 0, fallback = 0): number {
return Math.max(min, delay ?? fallback);
}
const getTouchFromPressEvent = (event: PressEvent) => {
const {changedTouches, touches} = event.nativeEvent;
@@ -31,24 +31,26 @@ function getMock<TArguments: $ReadOnlyArray<mixed>, TReturn>(
const createMockPressability = overrides => {
const config = {
getHitSlop: jest.fn(),
getHoverInDelayMS: jest.fn(() => null),
getHoverOutDelayMS: jest.fn(() => null),
getLongPressDelayMS: jest.fn(() => null),
getPressDelayMS: jest.fn(() => null),
getPressOutDelayMS: jest.fn(() => null),
getPressRectOffset: jest.fn(),
cancelable: null,
disabled: null,
hitSlop: null,
pressRectOffset: null,
delayHoverIn: null,
delayHoverOut: null,
delayLongPress: null,
delayPressIn: null,
delayPressOut: null,
onBlur: jest.fn(),
onFocus: jest.fn(),
onHoverIn: jest.fn(),
onHoverOut: jest.fn(),
onLongPress: jest.fn(),
onLongPressShouldCancelPress: jest.fn(),
onPress: jest.fn(),
onPressIn: jest.fn(),
onPressOut: jest.fn(),
onResponderTerminationRequest: jest.fn(() => true),
onStartShouldSetResponder: jest.fn(() => true),
onLongPressShouldCancelPress_DEPRECATED: jest.fn(),
onResponderTerminationRequest_DEPRECATED: jest.fn(() => true),
onStartShouldSetResponder_DEPRECATED: jest.fn(() => true),
...overrides,
};
const touchable = new Pressability(config);
@@ -282,7 +284,7 @@ describe('Pressability', () => {
it('is called after no delay by default', () => {
const {config, handlers} = createMockPressability({
getHoverInDelayMS: () => null,
delayHoverIn: null,
});
invariant(
typeof handlers.onMouseEnter === 'function',
@@ -293,9 +295,9 @@ describe('Pressability', () => {
expect(config.onHoverIn).toBeCalled();
});
it('falls back to no delay if `getHoverInDelayMS` is omitted', () => {
it('falls back to no delay if `delayHoverIn` is omitted', () => {
const {config, handlers} = createMockPressability({
getHoverInDelayMS: null,
delayHoverIn: null,
});
invariant(
typeof handlers.onMouseEnter === 'function',
@@ -308,7 +310,7 @@ describe('Pressability', () => {
it('is called after a configured delay', () => {
const {config, handlers} = createMockPressability({
getHoverInDelayMS: () => 500,
delayHoverIn: 500,
});
invariant(
typeof handlers.onMouseEnter === 'function',
@@ -324,7 +326,7 @@ describe('Pressability', () => {
it('is called synchronously if delay is 0ms', () => {
const {config, handlers} = createMockPressability({
getHoverInDelayMS: () => 0,
delayHoverIn: 0,
});
invariant(
typeof handlers.onMouseEnter === 'function',
@@ -354,7 +356,7 @@ describe('Pressability', () => {
it('is called if pressed for 500ms after the press delay', () => {
const {config, handlers} = createMockPressability({
getPressDelayMS: () => 100,
delayPressIn: 100,
});
handlers.onStartShouldSetResponder();
@@ -383,7 +385,7 @@ describe('Pressability', () => {
it('falls back to a minimum of 10ms before calling `onLongPress`', () => {
const {config, handlers} = createMockPressability({
getLongPressDelayMS: () => 0,
delayLongPress: 0,
});
handlers.onStartShouldSetResponder();
@@ -432,7 +434,7 @@ describe('Pressability', () => {
it('is called after no delay by default', () => {
const {config, handlers} = createMockPressability({
getPressDelayMS: () => null,
delayPressIn: null,
});
handlers.onStartShouldSetResponder();
@@ -442,9 +444,9 @@ describe('Pressability', () => {
expect(config.onPressIn).toBeCalled();
});
it('falls back to no delay if `getPressDelayMS` is omitted', () => {
it('falls back to no delay if `delayPressIn` is omitted', () => {
const {config, handlers} = createMockPressability({
getPressDelayMS: null,
delayPressIn: null,
});
handlers.onStartShouldSetResponder();
@@ -456,7 +458,7 @@ describe('Pressability', () => {
it('is called after a configured delay', () => {
const {config, handlers} = createMockPressability({
getPressDelayMS: () => 500,
delayPressIn: 500,
});
handlers.onStartShouldSetResponder();
@@ -471,7 +473,7 @@ describe('Pressability', () => {
it('is called synchronously if delay is 0ms', () => {
const {config, handlers} = createMockPressability({
getPressDelayMS: () => 0,
delayPressIn: 0,
});
handlers.onStartShouldSetResponder();
@@ -489,7 +491,7 @@ describe('Pressability', () => {
it('`onPress*` are called when no delay', () => {
mockUIManagerMeasure();
const {config, handlers} = createMockPressability({
getHitSlop: () => mockSlop,
hitSlop: mockSlop,
});
handlers.onStartShouldSetResponder();
@@ -521,8 +523,8 @@ describe('Pressability', () => {
it('`onPress*` are called after a delay', () => {
mockUIManagerMeasure();
const {config, handlers} = createMockPressability({
getHitSlop: () => mockSlop,
getPressDelayMS: () => 500,
hitSlop: mockSlop,
delayPressIn: 500,
});
handlers.onStartShouldSetResponder();
@@ -584,7 +586,7 @@ describe('Pressability', () => {
it('`onPress*` are not called after a delay', () => {
mockUIManagerMeasure();
const {config, handlers} = createMockPressability({
getPressDelayMS: () => 500,
delayPressIn: 500,
});
handlers.onStartShouldSetResponder();
@@ -611,7 +613,7 @@ describe('Pressability', () => {
it('`onPress*` are called when press is released before measure completes', () => {
mockUIManagerMeasure({delay: 1000});
const {config, handlers} = createMockPressability({
getPressDelayMS: () => 500,
delayPressIn: 500,
});
handlers.onStartShouldSetResponder();
@@ -643,7 +645,7 @@ describe('Pressability', () => {
describe('onStartShouldSetResponder', () => {
it('if omitted the responder is set by default', () => {
const {handlers} = createMockPressability({
onStartShouldSetResponder: null,
onStartShouldSetResponder_DEPRECATED: null,
});
expect(handlers.onStartShouldSetResponder()).toBe(true);
@@ -651,11 +653,14 @@ describe('Pressability', () => {
it('if supplied it is called', () => {
const {config, handlers} = createMockPressability();
const onStartShouldSetResponder_DEPRECATED = nullthrows(
config.onStartShouldSetResponder_DEPRECATED,
);
nullthrows(config.onStartShouldSetResponder).mockReturnValue(false);
onStartShouldSetResponder_DEPRECATED.mockReturnValue(false);
expect(handlers.onStartShouldSetResponder()).toBe(false);
nullthrows(config.onStartShouldSetResponder).mockReturnValue(true);
onStartShouldSetResponder_DEPRECATED.mockReturnValue(true);
expect(handlers.onStartShouldSetResponder()).toBe(true);
});
});