mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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:
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;
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user