RN: New `TouchableNativeFeedback`
Summary: Launches a new implementation of `TouchableNativeFeedback`. It is implemented using `Pressability` and extends `React.Component`. Notably, `propTypes` no longer exist. Furthermore, `TouchableNativeFeedback` now behaves similar to `TouchableWithoutFeedback` on iOS (instead of rendering an error message). Changelog: [General] [Changed] - TouchableNativeFeedback overhauled as a class without propTypes. Also, replaced iOS error renderer. Reviewed By: TheSavior Differential Revision: D18715857 fbshipit-source-id: aa42c7547ac94340fde0ef30641cab7eb48ea81b
This commit is contained in:
Родитель
88ae24f719
Коммит
2185dd298a
|
@ -1,403 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import TouchableInjection from './TouchableInjection';
|
||||
|
||||
const Platform = require('../../Utilities/Platform');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const ReactNative = require('../../Renderer/shims/ReactNative');
|
||||
const Touchable = require('./Touchable');
|
||||
const TouchableWithoutFeedback = require('./TouchableWithoutFeedback');
|
||||
const View = require('../View/View');
|
||||
import {Commands} from '../View/ViewNativeComponent';
|
||||
|
||||
const createReactClass = require('create-react-class');
|
||||
const ensurePositiveDelayProps = require('./ensurePositiveDelayProps');
|
||||
const processColor = require('../../StyleSheet/processColor');
|
||||
|
||||
import type {Props as TouchableWithoutFeedbackProps} from './TouchableWithoutFeedback';
|
||||
import type {PressEvent} from '../../Types/CoreEventTypes';
|
||||
|
||||
const rippleBackgroundPropType = PropTypes.shape({
|
||||
type: PropTypes.oneOf(['RippleAndroid']),
|
||||
color: PropTypes.number,
|
||||
borderless: PropTypes.bool,
|
||||
});
|
||||
|
||||
const themeAttributeBackgroundPropType = PropTypes.shape({
|
||||
type: PropTypes.oneOf(['ThemeAttrAndroid']),
|
||||
attribute: PropTypes.string.isRequired,
|
||||
});
|
||||
|
||||
const backgroundPropType = PropTypes.oneOfType([
|
||||
rippleBackgroundPropType,
|
||||
themeAttributeBackgroundPropType,
|
||||
]);
|
||||
|
||||
const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
|
||||
|
||||
export type Props = $ReadOnly<{|
|
||||
...TouchableWithoutFeedbackProps,
|
||||
|
||||
background?: ?(
|
||||
| $ReadOnly<{|
|
||||
type: 'ThemeAttrAndroid',
|
||||
attribute:
|
||||
| 'selectableItemBackground'
|
||||
| 'selectableItemBackgroundBorderless',
|
||||
|}>
|
||||
| $ReadOnly<{|
|
||||
type: 'RippleAndroid',
|
||||
color: ?number,
|
||||
borderless: boolean,
|
||||
|}>
|
||||
),
|
||||
hasTVPreferredFocus?: ?boolean,
|
||||
nextFocusDown?: ?number,
|
||||
nextFocusForward?: ?number,
|
||||
nextFocusLeft?: ?number,
|
||||
nextFocusRight?: ?number,
|
||||
nextFocusUp?: ?number,
|
||||
useForeground?: ?boolean,
|
||||
|}>;
|
||||
|
||||
/**
|
||||
* A wrapper for making views respond properly to touches (Android only).
|
||||
* On Android this component uses native state drawable to display touch
|
||||
* feedback.
|
||||
*
|
||||
* At the moment it only supports having a single View instance as a child
|
||||
* node, as it's implemented by replacing that View with another instance of
|
||||
* RCTView node with some additional properties set.
|
||||
*
|
||||
* Background drawable of native feedback touchable can be customized with
|
||||
* `background` property.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* renderButton: function() {
|
||||
* return (
|
||||
* <TouchableNativeFeedback
|
||||
* onPress={this._onPressButton}
|
||||
* background={TouchableNativeFeedback.SelectableBackground()}>
|
||||
* <View style={{width: 150, height: 100, backgroundColor: 'red'}}>
|
||||
* <Text style={{margin: 30}}>Button</Text>
|
||||
* </View>
|
||||
* </TouchableNativeFeedback>
|
||||
* );
|
||||
* },
|
||||
* ```
|
||||
*/
|
||||
|
||||
const TouchableNativeFeedbackImpl = createReactClass({
|
||||
displayName: 'TouchableNativeFeedback',
|
||||
propTypes: {
|
||||
/* $FlowFixMe(>=0.89.0 site=react_native_android_fb) This comment
|
||||
* suppresses an error found when Flow v0.89 was deployed. To see the
|
||||
* error, delete this comment and run Flow. */
|
||||
...TouchableWithoutFeedback.propTypes,
|
||||
|
||||
/**
|
||||
* Determines the type of background drawable that's going to be used to
|
||||
* display feedback. It takes an object with `type` property and extra data
|
||||
* depending on the `type`. It's recommended to use one of the static
|
||||
* methods to generate that dictionary.
|
||||
*/
|
||||
background: backgroundPropType,
|
||||
|
||||
/**
|
||||
* TV preferred focus (see documentation for the View component).
|
||||
*/
|
||||
hasTVPreferredFocus: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* TV next focus down (see documentation for the View component).
|
||||
*/
|
||||
nextFocusDown: PropTypes.number,
|
||||
|
||||
/**
|
||||
* TV next focus forward (see documentation for the View component).
|
||||
*/
|
||||
nextFocusForward: PropTypes.number,
|
||||
|
||||
/**
|
||||
* TV next focus left (see documentation for the View component).
|
||||
*/
|
||||
nextFocusLeft: PropTypes.number,
|
||||
|
||||
/**
|
||||
* TV next focus right (see documentation for the View component).
|
||||
*/
|
||||
nextFocusRight: PropTypes.number,
|
||||
|
||||
/**
|
||||
* TV next focus up (see documentation for the View component).
|
||||
*/
|
||||
nextFocusUp: PropTypes.number,
|
||||
|
||||
/**
|
||||
* Set to true to add the ripple effect to the foreground of the view, instead of the
|
||||
* background. This is useful if one of your child views has a background of its own, or you're
|
||||
* e.g. displaying images, and you don't want the ripple to be covered by them.
|
||||
*
|
||||
* Check TouchableNativeFeedback.canUseNativeForeground() first, as this is only available on
|
||||
* Android 6.0 and above. If you try to use this on older versions you will get a warning and
|
||||
* fallback to background.
|
||||
*/
|
||||
useForeground: PropTypes.bool,
|
||||
},
|
||||
|
||||
statics: {
|
||||
/**
|
||||
* Creates an object that represents android theme's default background for
|
||||
* selectable elements (?android:attr/selectableItemBackground).
|
||||
*/
|
||||
SelectableBackground: function(): {
|
||||
type: 'ThemeAttrAndroid',
|
||||
attribute: 'selectableItemBackground',
|
||||
...
|
||||
} {
|
||||
return {type: 'ThemeAttrAndroid', attribute: 'selectableItemBackground'};
|
||||
},
|
||||
/**
|
||||
* Creates an object that represent android theme's default background for borderless
|
||||
* selectable elements (?android:attr/selectableItemBackgroundBorderless).
|
||||
* Available on android API level 21+.
|
||||
*/
|
||||
SelectableBackgroundBorderless: function(): {
|
||||
type: 'ThemeAttrAndroid',
|
||||
attribute: 'selectableItemBackgroundBorderless',
|
||||
...
|
||||
} {
|
||||
return {
|
||||
type: 'ThemeAttrAndroid',
|
||||
attribute: 'selectableItemBackgroundBorderless',
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Creates an object that represents ripple drawable with specified color (as a
|
||||
* string). If property `borderless` evaluates to true the ripple will
|
||||
* render outside of the view bounds (see native actionbar buttons as an
|
||||
* example of that behavior). This background type is available on Android
|
||||
* API level 21+.
|
||||
*
|
||||
* @param color The ripple color
|
||||
* @param borderless If the ripple can render outside it's bounds
|
||||
*/
|
||||
Ripple: function(
|
||||
color: string,
|
||||
borderless: boolean,
|
||||
): {
|
||||
type: 'RippleAndroid',
|
||||
color: ?number,
|
||||
borderless: boolean,
|
||||
...
|
||||
} {
|
||||
return {
|
||||
type: 'RippleAndroid',
|
||||
color: processColor(color),
|
||||
borderless: borderless,
|
||||
};
|
||||
},
|
||||
|
||||
canUseNativeForeground: function(): boolean {
|
||||
return Platform.OS === 'android' && Platform.Version >= 23;
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [Touchable.Mixin],
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
background: this.SelectableBackground(),
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.touchableGetInitialState();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
ensurePositiveDelayProps(this.props);
|
||||
},
|
||||
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
ensurePositiveDelayProps(nextProps);
|
||||
},
|
||||
|
||||
/**
|
||||
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
|
||||
* defined on your component.
|
||||
*/
|
||||
touchableHandleActivePressIn: function(e: PressEvent) {
|
||||
this.props.onPressIn && this.props.onPressIn(e);
|
||||
this._dispatchPressedStateChange(true);
|
||||
if (this.pressInLocation) {
|
||||
this._dispatchHotspotUpdate(
|
||||
this.pressInLocation.locationX,
|
||||
this.pressInLocation.locationY,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
touchableHandleActivePressOut: function(e: PressEvent) {
|
||||
this.props.onPressOut && this.props.onPressOut(e);
|
||||
this._dispatchPressedStateChange(false);
|
||||
},
|
||||
|
||||
touchableHandlePress: function(e: PressEvent) {
|
||||
this.props.onPress && this.props.onPress(e);
|
||||
},
|
||||
|
||||
touchableHandleLongPress: function(e: PressEvent) {
|
||||
this.props.onLongPress && this.props.onLongPress(e);
|
||||
},
|
||||
|
||||
touchableGetPressRectOffset: function() {
|
||||
// Always make sure to predeclare a constant!
|
||||
return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
|
||||
},
|
||||
|
||||
touchableGetHitSlop: function() {
|
||||
return this.props.hitSlop;
|
||||
},
|
||||
|
||||
touchableGetHighlightDelayMS: function() {
|
||||
return this.props.delayPressIn;
|
||||
},
|
||||
|
||||
touchableGetLongPressDelayMS: function() {
|
||||
return this.props.delayLongPress;
|
||||
},
|
||||
|
||||
touchableGetPressOutDelayMS: function() {
|
||||
return this.props.delayPressOut;
|
||||
},
|
||||
|
||||
_handleResponderMove: function(e) {
|
||||
this.touchableHandleResponderMove(e);
|
||||
this._dispatchHotspotUpdate(
|
||||
e.nativeEvent.locationX,
|
||||
e.nativeEvent.locationY,
|
||||
);
|
||||
},
|
||||
|
||||
_dispatchHotspotUpdate: function(destX, destY) {
|
||||
const hostComponentRef = ReactNative.findHostInstance_DEPRECATED(this);
|
||||
|
||||
if (hostComponentRef == null) {
|
||||
console.warn(
|
||||
'Touchable: Unable to find HostComponent instance. ' +
|
||||
'Has your Touchable component been unmounted?',
|
||||
);
|
||||
} else {
|
||||
Commands.hotspotUpdate(hostComponentRef, destX || 0, destY || 0);
|
||||
}
|
||||
},
|
||||
|
||||
_dispatchPressedStateChange: function(pressed) {
|
||||
const hostComponentRef = ReactNative.findHostInstance_DEPRECATED(this);
|
||||
if (hostComponentRef == null) {
|
||||
console.warn(
|
||||
'Touchable: Unable to find HostComponent instance. ' +
|
||||
'Has your Touchable component been unmounted?',
|
||||
);
|
||||
} else {
|
||||
Commands.setPressed(hostComponentRef, pressed);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const child = React.Children.only(this.props.children);
|
||||
let children = child.props.children;
|
||||
if (Touchable.TOUCH_TARGET_DEBUG && child.type === View) {
|
||||
if (!Array.isArray(children)) {
|
||||
children = [children];
|
||||
}
|
||||
children.push(
|
||||
Touchable.renderDebugView({
|
||||
color: 'brown',
|
||||
hitSlop: this.props.hitSlop,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (
|
||||
this.props.useForeground &&
|
||||
!TouchableNativeFeedbackImpl.canUseNativeForeground()
|
||||
) {
|
||||
console.warn(
|
||||
'Requested foreground ripple, but it is not available on this version of Android. ' +
|
||||
'Consider calling TouchableNativeFeedback.canUseNativeForeground() and using a different ' +
|
||||
'Touchable if the result is false.',
|
||||
);
|
||||
}
|
||||
const drawableProp =
|
||||
this.props.useForeground &&
|
||||
TouchableNativeFeedbackImpl.canUseNativeForeground()
|
||||
? 'nativeForegroundAndroid'
|
||||
: 'nativeBackgroundAndroid';
|
||||
const childProps = {
|
||||
...child.props,
|
||||
/* $FlowFixMe(>=0.111.0 site=react_native_android_fb) This comment
|
||||
* suppresses an error found when Flow v0.111 was deployed. To see the
|
||||
* error, delete this comment and run Flow. */
|
||||
[drawableProp]: this.props.background,
|
||||
accessible: this.props.accessible !== false,
|
||||
accessibilityLabel: this.props.accessibilityLabel,
|
||||
accessibilityRole: this.props.accessibilityRole,
|
||||
accessibilityState: this.props.accessibilityState,
|
||||
accessibilityActions: this.props.accessibilityActions,
|
||||
onAccessibilityAction: this.props.onAccessibilityAction,
|
||||
accessibilityValue: this.props.accessibilityValue,
|
||||
importantForAccessibility: this.props.importantForAccessibility,
|
||||
accessibilityLiveRegion: this.props.accessibilityLiveRegion,
|
||||
accessibilityViewIsModal: this.props.accessibilityViewIsModal,
|
||||
accessibilityElementsHidden: this.props.accessibilityElementsHidden,
|
||||
children,
|
||||
testID: this.props.testID,
|
||||
onLayout: this.props.onLayout,
|
||||
hitSlop: this.props.hitSlop,
|
||||
nextFocusDown: this.props.nextFocusDown,
|
||||
nextFocusForward: this.props.nextFocusForward,
|
||||
nextFocusLeft: this.props.nextFocusLeft,
|
||||
nextFocusRight: this.props.nextFocusRight,
|
||||
nextFocusUp: this.props.nextFocusUp,
|
||||
hasTVPreferredFocus: this.props.hasTVPreferredFocus,
|
||||
focusable:
|
||||
this.props.focusable !== false &&
|
||||
this.props.onPress !== undefined &&
|
||||
!this.props.disabled,
|
||||
onClick: this.touchableHandlePress,
|
||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest: this
|
||||
.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: this.touchableHandleResponderGrant,
|
||||
onResponderMove: this._handleResponderMove,
|
||||
onResponderRelease: this.touchableHandleResponderRelease,
|
||||
onResponderTerminate: this.touchableHandleResponderTerminate,
|
||||
};
|
||||
|
||||
// We need to clone the actual element so that the ripple background drawable
|
||||
// can be applied directly to the background of this element rather than to
|
||||
// a wrapper view as done in other Touchable*
|
||||
return React.cloneElement(child, childProps);
|
||||
},
|
||||
});
|
||||
|
||||
const TouchableNativeFeedback: React.ComponentType<Props> =
|
||||
TouchableInjection.unstable_TouchableNativeFeedback == null
|
||||
? TouchableNativeFeedbackImpl
|
||||
: TouchableInjection.unstable_TouchableNativeFeedback;
|
||||
|
||||
module.exports = (TouchableNativeFeedback: $FlowFixMe);
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
const StyleSheet = require('../../StyleSheet/StyleSheet');
|
||||
const Text = require('../../Text/Text');
|
||||
const View = require('../View/View');
|
||||
|
||||
class DummyTouchableNativeFeedback extends React.Component {
|
||||
static SelectableBackground = () => ({});
|
||||
static SelectableBackgroundBorderless = () => ({});
|
||||
static Ripple = () => ({});
|
||||
static canUseNativeForeground = () => false;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={[styles.container, this.props.style]}>
|
||||
<Text style={styles.info}>
|
||||
TouchableNativeFeedback is not supported on this platform!
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
height: 100,
|
||||
width: 300,
|
||||
backgroundColor: '#ffbcbc',
|
||||
borderWidth: 1,
|
||||
borderColor: 'red',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: 10,
|
||||
},
|
||||
info: {
|
||||
color: '#333333',
|
||||
margin: 20,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = DummyTouchableNativeFeedback;
|
|
@ -0,0 +1,331 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Pressability from '../../Pressability/Pressability.js';
|
||||
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug.js';
|
||||
import TVTouchable from './TVTouchable.js';
|
||||
import type {Props as TouchableWithoutFeedbackProps} from './TouchableWithoutFeedback.js';
|
||||
import {Commands} from 'react-native/Libraries/Components/View/ViewNativeComponent';
|
||||
import ReactNative from 'react-native/Libraries/Renderer/shims/ReactNative';
|
||||
import type {PressEvent} from 'react-native/Libraries/Types/CoreEventTypes';
|
||||
import {Platform, View, processColor} from 'react-native';
|
||||
import * as React from 'react';
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
...TouchableWithoutFeedbackProps,
|
||||
|
||||
/**
|
||||
* Determines the type of background drawable that's going to be used to
|
||||
* display feedback. It takes an object with `type` property and extra data
|
||||
* depending on the `type`. It's recommended to use one of the static
|
||||
* methods to generate that dictionary.
|
||||
*/
|
||||
background?: ?(
|
||||
| $ReadOnly<{|
|
||||
type: 'ThemeAttrAndroid',
|
||||
attribute:
|
||||
| 'selectableItemBackground'
|
||||
| 'selectableItemBackgroundBorderless',
|
||||
|}>
|
||||
| $ReadOnly<{|
|
||||
type: 'RippleAndroid',
|
||||
color: ?number,
|
||||
borderless: boolean,
|
||||
|}>
|
||||
),
|
||||
|
||||
/**
|
||||
* TV preferred focus (see documentation for the View component).
|
||||
*/
|
||||
hasTVPreferredFocus?: ?boolean,
|
||||
|
||||
/**
|
||||
* TV next focus down (see documentation for the View component).
|
||||
*/
|
||||
nextFocusDown?: ?number,
|
||||
|
||||
/**
|
||||
* TV next focus forward (see documentation for the View component).
|
||||
*/
|
||||
nextFocusForward?: ?number,
|
||||
|
||||
/**
|
||||
* TV next focus left (see documentation for the View component).
|
||||
*/
|
||||
nextFocusLeft?: ?number,
|
||||
|
||||
/**
|
||||
* TV next focus right (see documentation for the View component).
|
||||
*/
|
||||
nextFocusRight?: ?number,
|
||||
|
||||
/**
|
||||
* TV next focus up (see documentation for the View component).
|
||||
*/
|
||||
nextFocusUp?: ?number,
|
||||
|
||||
/**
|
||||
* Set to true to add the ripple effect to the foreground of the view, instead
|
||||
* of the background. This is useful if one of your child views has a
|
||||
* background of its own, or you're e.g. displaying images, and you don't want
|
||||
* the ripple to be covered by them.
|
||||
*
|
||||
* Check TouchableNativeFeedback.canUseNativeForeground() first, as this is
|
||||
* only available on Android 6.0 and above. If you try to use this on older
|
||||
* versions, this will fallback to background.
|
||||
*/
|
||||
useForeground?: ?boolean,
|
||||
|}>;
|
||||
|
||||
type State = $ReadOnly<{|
|
||||
pressability: Pressability,
|
||||
|}>;
|
||||
|
||||
class TouchableNativeFeedback extends React.Component<Props, State> {
|
||||
/**
|
||||
* Creates a value for the `background` prop that uses the Android theme's
|
||||
* default background for selectable elements.
|
||||
*/
|
||||
static SelectableBackground: () => $ReadOnly<{|
|
||||
attribute: 'selectableItemBackground',
|
||||
type: 'ThemeAttrAndroid',
|
||||
|}> = () => ({
|
||||
type: 'ThemeAttrAndroid',
|
||||
attribute: 'selectableItemBackground',
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a value for the `background` prop that uses the Android theme's
|
||||
* default background for borderless selectable elements. Requires API 21+.
|
||||
*/
|
||||
static SelectableBackgroundBorderless: () => $ReadOnly<{|
|
||||
attribute: 'selectableItemBackgroundBorderless',
|
||||
type: 'ThemeAttrAndroid',
|
||||
|}> = () => ({
|
||||
type: 'ThemeAttrAndroid',
|
||||
attribute: 'selectableItemBackgroundBorderless',
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a value for the `background` prop that uses the Android ripple with
|
||||
* the supplied color. If `borderless` is true, the ripple will render outside
|
||||
* of the view bounds. Requires API 21+.
|
||||
*/
|
||||
static Ripple: (
|
||||
color: string,
|
||||
borderless: boolean,
|
||||
) => $ReadOnly<{|
|
||||
borderless: boolean,
|
||||
color: ?number,
|
||||
type: 'RippleAndroid',
|
||||
|}> = (color: string, borderless: boolean) => ({
|
||||
type: 'RippleAndroid',
|
||||
color: processColor(color),
|
||||
borderless,
|
||||
});
|
||||
|
||||
/**
|
||||
* Whether `useForeground` is supported.
|
||||
*/
|
||||
static canUseNativeForeground: () => boolean = () =>
|
||||
Platform.OS === 'android' && Platform.Version >= 23;
|
||||
|
||||
_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);
|
||||
}
|
||||
},
|
||||
onPressIn: event => {
|
||||
if (Platform.OS === 'android') {
|
||||
this._dispatchPressedStateChange(true);
|
||||
this._dispatchHotspotUpdate(event);
|
||||
}
|
||||
if (this.props.onPressIn != null) {
|
||||
this.props.onPressIn(event);
|
||||
}
|
||||
},
|
||||
onPressMove: event => {
|
||||
if (Platform.OS === 'android') {
|
||||
this._dispatchHotspotUpdate(event);
|
||||
}
|
||||
},
|
||||
onPressOut: event => {
|
||||
if (Platform.OS === 'android') {
|
||||
this._dispatchPressedStateChange(false);
|
||||
}
|
||||
if (this.props.onPressOut != null) {
|
||||
this.props.onPressOut(event);
|
||||
}
|
||||
},
|
||||
onResponderTerminationRequest: () =>
|
||||
!this.props.rejectResponderTermination,
|
||||
onStartShouldSetResponder: () => !this.props.disabled,
|
||||
}),
|
||||
};
|
||||
|
||||
_dispatchPressedStateChange(pressed: boolean): void {
|
||||
if (Platform.OS === 'android') {
|
||||
const hostComponentRef = ReactNative.findHostInstance_DEPRECATED(this);
|
||||
if (hostComponentRef == null) {
|
||||
console.warn(
|
||||
'Touchable: Unable to find HostComponent instance. ' +
|
||||
'Has your Touchable component been unmounted?',
|
||||
);
|
||||
} else {
|
||||
Commands.setPressed(hostComponentRef, pressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_dispatchHotspotUpdate(event: PressEvent): void {
|
||||
if (Platform.OS === 'android') {
|
||||
const {locationX, locationY} = event.nativeEvent;
|
||||
const hostComponentRef = ReactNative.findHostInstance_DEPRECATED(this);
|
||||
if (hostComponentRef == null) {
|
||||
console.warn(
|
||||
'Touchable: Unable to find HostComponent instance. ' +
|
||||
'Has your Touchable component been unmounted?',
|
||||
);
|
||||
} else {
|
||||
Commands.hotspotUpdate(
|
||||
hostComponentRef,
|
||||
locationX ?? 0,
|
||||
locationY ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const element = React.Children.only(this.props.children);
|
||||
const children = [element.props.children];
|
||||
if (__DEV__) {
|
||||
if (element.type === View) {
|
||||
children.push(
|
||||
<PressabilityDebugView color="brown" hitSlop={this.props.hitSlop} />,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
|
||||
// adopting `Pressability`, so preserve that behavior.
|
||||
const {
|
||||
onBlur,
|
||||
onFocus,
|
||||
...eventHandlersWithoutBlurAndFocus
|
||||
} = this.state.pressability.getEventHandlers();
|
||||
|
||||
return React.cloneElement(
|
||||
element,
|
||||
{
|
||||
...eventHandlersWithoutBlurAndFocus,
|
||||
...getBackgroundProp(
|
||||
this.props.background === undefined
|
||||
? TouchableNativeFeedback.SelectableBackground()
|
||||
: this.props.background,
|
||||
this.props.useForeground === true,
|
||||
),
|
||||
accessible: this.props.accessible !== false,
|
||||
accessibilityLabel: this.props.accessibilityLabel,
|
||||
accessibilityRole: this.props.accessibilityRole,
|
||||
accessibilityState: this.props.accessibilityState,
|
||||
accessibilityActions: this.props.accessibilityActions,
|
||||
onAccessibilityAction: this.props.onAccessibilityAction,
|
||||
accessibilityValue: this.props.accessibilityValue,
|
||||
importantForAccessibility: this.props.importantForAccessibility,
|
||||
accessibilityLiveRegion: this.props.accessibilityLiveRegion,
|
||||
accessibilityViewIsModal: this.props.accessibilityViewIsModal,
|
||||
accessibilityElementsHidden: this.props.accessibilityElementsHidden,
|
||||
hasTVPreferredFocus: this.props.hasTVPreferredFocus,
|
||||
hitSlop: this.props.hitSlop,
|
||||
focusable:
|
||||
this.props.focusable !== false &&
|
||||
this.props.onPress !== undefined &&
|
||||
!this.props.disabled,
|
||||
nativeID: this.props.nativeID,
|
||||
nextFocusDown: this.props.nextFocusDown,
|
||||
nextFocusForward: this.props.nextFocusForward,
|
||||
nextFocusLeft: this.props.nextFocusLeft,
|
||||
nextFocusRight: this.props.nextFocusRight,
|
||||
nextFocusUp: this.props.nextFocusUp,
|
||||
onLayout: this.props.onLayout,
|
||||
testID: this.props.testID,
|
||||
},
|
||||
...children,
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
if (Platform.isTV) {
|
||||
this._tvTouchable = new TVTouchable(this, {
|
||||
getDisabled: () => this.props.disabled === true,
|
||||
onBlur: event => {
|
||||
if (this.props.onBlur != null) {
|
||||
this.props.onBlur(event);
|
||||
}
|
||||
},
|
||||
onFocus: event => {
|
||||
if (this.props.onFocus != null) {
|
||||
this.props.onFocus(event);
|
||||
}
|
||||
},
|
||||
onPress: event => {
|
||||
if (this.props.onPress != null) {
|
||||
this.props.onPress(event);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (Platform.isTV) {
|
||||
if (this._tvTouchable != null) {
|
||||
this._tvTouchable.destroy();
|
||||
}
|
||||
}
|
||||
this.state.pressability.reset();
|
||||
}
|
||||
}
|
||||
|
||||
const getBackgroundProp =
|
||||
Platform.OS === 'android'
|
||||
? (background, useForeground) =>
|
||||
useForeground && TouchableNativeFeedback.canUseNativeForeground()
|
||||
? {nativeForegroundAndroid: background}
|
||||
: {nativeBackgroundAndroid: background}
|
||||
: (background, useForeground) => null;
|
||||
|
||||
module.exports = TouchableNativeFeedback;
|
|
@ -402,10 +402,9 @@ class TouchableDisabled extends React.Component<{...}> {
|
|||
|
||||
{Platform.OS === 'android' && (
|
||||
<TouchableNativeFeedback
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom TNF has been clicked')}
|
||||
background={TouchableNativeFeedback.SelectableBackground()}>
|
||||
<View>
|
||||
<View style={[styles.row, styles.block]}>
|
||||
<Text style={[styles.button, styles.nativeFeedbackButton]}>
|
||||
Enabled TouchableNativeFeedback
|
||||
</Text>
|
||||
|
@ -416,10 +415,9 @@ class TouchableDisabled extends React.Component<{...}> {
|
|||
{Platform.OS === 'android' && (
|
||||
<TouchableNativeFeedback
|
||||
disabled={true}
|
||||
style={[styles.row, styles.block]}
|
||||
onPress={() => console.log('custom TNF has been clicked')}
|
||||
background={TouchableNativeFeedback.SelectableBackground()}>
|
||||
<View>
|
||||
<View style={[styles.row, styles.block]}>
|
||||
<Text
|
||||
style={[styles.disabledButton, styles.nativeFeedbackButton]}>
|
||||
Disabled TouchableNativeFeedback
|
||||
|
|
Загрузка…
Ссылка в новой задаче