mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-25 07:34:45 +00:00
[change] Update Touchable components
Reference RN 0.60 implementations with web-specific fixes Ref #1172 Fix #1105
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
"window": false,
|
||||
// Flow global types,
|
||||
"$Enum": false,
|
||||
"$ReadOnly": false,
|
||||
"CSSStyleSheet": false,
|
||||
"HTMLInputElement": false,
|
||||
"ReactClass": false,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<PROJECT_ROOT>/.*/__tests__/.*
|
||||
<PROJECT_ROOT>/packages/.*/dist/.*
|
||||
<PROJECT_ROOT>/packages/examples/.*
|
||||
<PROJECT_ROOT>/packages/react-native-web/src/vendor/.*
|
||||
<PROJECT_ROOT>/packages/website/.*
|
||||
.*/node_modules/babel-plugin-transform-react-remove-prop-types/*
|
||||
|
||||
|
||||
+180
-85
@@ -1,26 +1,42 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
/**
|
||||
* Copyright (c) Nicolas Gallagher.
|
||||
* 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
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import AccessibilityUtil from '../../modules/AccessibilityUtil';
|
||||
import BoundingDimensions from './BoundingDimensions';
|
||||
import findNodeHandle from '../findNodeHandle';
|
||||
import normalizeColor from 'normalize-css-color';
|
||||
import Position from './Position';
|
||||
import React from 'react';
|
||||
import TouchEventUtils from 'fbjs/lib/TouchEventUtils';
|
||||
import UIManager from '../UIManager';
|
||||
import View from '../View';
|
||||
|
||||
type Event = Object;
|
||||
type PressEvent = Object;
|
||||
type EdgeInsetsProp = Object;
|
||||
|
||||
const extractSingleTouch = nativeEvent => {
|
||||
const touches = nativeEvent.touches;
|
||||
const changedTouches = nativeEvent.changedTouches;
|
||||
const hasTouches = touches && touches.length > 0;
|
||||
const hasChangedTouches = changedTouches && changedTouches.length > 0;
|
||||
|
||||
return !hasTouches && hasChangedTouches
|
||||
? changedTouches[0]
|
||||
: hasTouches
|
||||
? touches[0]
|
||||
: nativeEvent;
|
||||
};
|
||||
|
||||
/**
|
||||
* `Touchable`: Taps done right.
|
||||
@@ -110,6 +126,7 @@ type Event = Object;
|
||||
/**
|
||||
* Touchable states.
|
||||
*/
|
||||
|
||||
const States = {
|
||||
NOT_RESPONDER: 'NOT_RESPONDER', // Not the responder
|
||||
RESPONDER_INACTIVE_PRESS_IN: 'RESPONDER_INACTIVE_PRESS_IN', // Responder, inactive, in the `PressRect`
|
||||
@@ -121,10 +138,33 @@ const States = {
|
||||
ERROR: 'ERROR'
|
||||
};
|
||||
|
||||
/**
|
||||
type State =
|
||||
| typeof States.NOT_RESPONDER
|
||||
| typeof States.RESPONDER_INACTIVE_PRESS_IN
|
||||
| typeof States.RESPONDER_INACTIVE_PRESS_OUT
|
||||
| typeof States.RESPONDER_ACTIVE_PRESS_IN
|
||||
| typeof States.RESPONDER_ACTIVE_PRESS_OUT
|
||||
| typeof States.RESPONDER_ACTIVE_LONG_PRESS_IN
|
||||
| typeof States.RESPONDER_ACTIVE_LONG_PRESS_OUT
|
||||
| typeof States.ERROR;
|
||||
|
||||
/*
|
||||
* Quick lookup map for states that are considered to be "active"
|
||||
*/
|
||||
|
||||
const baseStatesConditions = {
|
||||
NOT_RESPONDER: false,
|
||||
RESPONDER_INACTIVE_PRESS_IN: false,
|
||||
RESPONDER_INACTIVE_PRESS_OUT: false,
|
||||
RESPONDER_ACTIVE_PRESS_IN: false,
|
||||
RESPONDER_ACTIVE_PRESS_OUT: false,
|
||||
RESPONDER_ACTIVE_LONG_PRESS_IN: false,
|
||||
RESPONDER_ACTIVE_LONG_PRESS_OUT: false,
|
||||
ERROR: false
|
||||
};
|
||||
|
||||
const IsActive = {
|
||||
...baseStatesConditions,
|
||||
RESPONDER_ACTIVE_PRESS_OUT: true,
|
||||
RESPONDER_ACTIVE_PRESS_IN: true
|
||||
};
|
||||
@@ -134,12 +174,14 @@ const IsActive = {
|
||||
* therefore eligible to result in a "selection" if the press stops.
|
||||
*/
|
||||
const IsPressingIn = {
|
||||
...baseStatesConditions,
|
||||
RESPONDER_INACTIVE_PRESS_IN: true,
|
||||
RESPONDER_ACTIVE_PRESS_IN: true,
|
||||
RESPONDER_ACTIVE_LONG_PRESS_IN: true
|
||||
};
|
||||
|
||||
const IsLongPressingIn = {
|
||||
...baseStatesConditions,
|
||||
RESPONDER_ACTIVE_LONG_PRESS_IN: true
|
||||
};
|
||||
|
||||
@@ -156,6 +198,15 @@ const Signals = {
|
||||
LONG_PRESS_DETECTED: 'LONG_PRESS_DETECTED'
|
||||
};
|
||||
|
||||
type Signal =
|
||||
| typeof Signals.DELAY
|
||||
| typeof Signals.RESPONDER_GRANT
|
||||
| typeof Signals.RESPONDER_RELEASE
|
||||
| typeof Signals.RESPONDER_TERMINATED
|
||||
| typeof Signals.ENTER_PRESS_RECT
|
||||
| typeof Signals.LEAVE_PRESS_RECT
|
||||
| typeof Signals.LONG_PRESS_DETECTED;
|
||||
|
||||
/**
|
||||
* Mapping from States x Signals => States
|
||||
*/
|
||||
@@ -382,13 +433,16 @@ const TouchableMixin = {
|
||||
|
||||
/**
|
||||
* Place as callback for a DOM element's `onResponderGrant` event.
|
||||
* @param {SyntheticEvent} e Synthetic event from event system.
|
||||
*
|
||||
*/
|
||||
touchableHandleResponderGrant: function(e: Event) {
|
||||
touchableHandleResponderGrant: function(e: PressEvent) {
|
||||
const dispatchID = e.currentTarget;
|
||||
// Since e is used in a callback invoked on another event loop
|
||||
// (as in setTimeout etc), we need to call e.persist() on the
|
||||
// event to make sure it doesn't get reused in the event object pool.
|
||||
e.persist();
|
||||
|
||||
this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout);
|
||||
this.pressOutDelayTimeout = null;
|
||||
|
||||
@@ -403,7 +457,6 @@ const TouchableMixin = {
|
||||
if (delayMS !== 0) {
|
||||
this.touchableDelayTimeout = setTimeout(this._handleDelay.bind(this, e), delayMS);
|
||||
} else {
|
||||
this.state.touchable.positionOnActivate = null;
|
||||
this._handleDelay(e);
|
||||
}
|
||||
|
||||
@@ -421,27 +474,23 @@ const TouchableMixin = {
|
||||
/**
|
||||
* Place as callback for a DOM element's `onResponderRelease` event.
|
||||
*/
|
||||
touchableHandleResponderRelease: function(e: Event) {
|
||||
touchableHandleResponderRelease: function(e: PressEvent) {
|
||||
this.pressInLocation = null;
|
||||
this._receiveSignal(Signals.RESPONDER_RELEASE, e);
|
||||
},
|
||||
|
||||
/**
|
||||
* Place as callback for a DOM element's `onResponderTerminate` event.
|
||||
*/
|
||||
touchableHandleResponderTerminate: function(e: Event) {
|
||||
touchableHandleResponderTerminate: function(e: PressEvent) {
|
||||
this.pressInLocation = null;
|
||||
this._receiveSignal(Signals.RESPONDER_TERMINATED, e);
|
||||
},
|
||||
|
||||
/**
|
||||
* Place as callback for a DOM element's `onResponderMove` event.
|
||||
*/
|
||||
touchableHandleResponderMove: function(e: Event) {
|
||||
// Not enough time elapsed yet, wait for highlight -
|
||||
// this is just a perf optimization.
|
||||
if (this.state.touchable.touchState === States.RESPONDER_INACTIVE_PRESS_IN) {
|
||||
return;
|
||||
}
|
||||
|
||||
touchableHandleResponderMove: function(e: PressEvent) {
|
||||
// Measurement may not have returned yet.
|
||||
if (!this.state.touchable.positionOnActivate) {
|
||||
return;
|
||||
@@ -466,13 +515,13 @@ const TouchableMixin = {
|
||||
const hitSlop = this.touchableGetHitSlop ? this.touchableGetHitSlop() : null;
|
||||
|
||||
if (hitSlop) {
|
||||
pressExpandLeft += hitSlop.left;
|
||||
pressExpandTop += hitSlop.top;
|
||||
pressExpandRight += hitSlop.right;
|
||||
pressExpandBottom += hitSlop.bottom;
|
||||
pressExpandLeft += hitSlop.left || 0;
|
||||
pressExpandTop += hitSlop.top || 0;
|
||||
pressExpandRight += hitSlop.right || 0;
|
||||
pressExpandBottom += hitSlop.bottom || 0;
|
||||
}
|
||||
|
||||
const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
|
||||
const touch = extractSingleTouch(e.nativeEvent);
|
||||
const pageX = touch && touch.pageX;
|
||||
const pageY = touch && touch.pageY;
|
||||
|
||||
@@ -494,9 +543,13 @@ const TouchableMixin = {
|
||||
pageX < positionOnActivate.left + dimensionsOnActivate.width + pressExpandRight &&
|
||||
pageY < positionOnActivate.top + dimensionsOnActivate.height + pressExpandBottom;
|
||||
if (isTouchWithinActive) {
|
||||
const prevState = this.state.touchable.touchState;
|
||||
this._receiveSignal(Signals.ENTER_PRESS_RECT, e);
|
||||
const curState = this.state.touchable.touchState;
|
||||
if (curState === States.RESPONDER_INACTIVE_PRESS_IN) {
|
||||
if (
|
||||
curState === States.RESPONDER_INACTIVE_PRESS_IN &&
|
||||
prevState !== States.RESPONDER_INACTIVE_PRESS_IN
|
||||
) {
|
||||
// fix for t7967420
|
||||
this._cancelLongPressDelayTimeout();
|
||||
}
|
||||
@@ -506,6 +559,30 @@ const TouchableMixin = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked when the item receives focus. Mixers might override this to
|
||||
* visually distinguish the `VisualRect` so that the user knows that it
|
||||
* currently has the focus. Most platforms only support a single element being
|
||||
* focused at a time, in which case there may have been a previously focused
|
||||
* element that was blurred just prior to this. This can be overridden when
|
||||
* using `Touchable.Mixin.withoutDefaultFocusAndBlur`.
|
||||
*/
|
||||
touchableHandleFocus: function(e: Event) {
|
||||
this.props.onFocus && this.props.onFocus(e);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked when the item loses focus. Mixers might override this to
|
||||
* visually distinguish the `VisualRect` so that the user knows that it
|
||||
* no longer has focus. Most platforms only support a single element being
|
||||
* focused at a time, in which case the focus may have moved to another.
|
||||
* This can be overridden when using
|
||||
* `Touchable.Mixin.withoutDefaultFocusAndBlur`.
|
||||
*/
|
||||
touchableHandleBlur: function(e: Event) {
|
||||
this.props.onBlur && this.props.onBlur(e);
|
||||
},
|
||||
|
||||
// ==== Abstract Application Callbacks ====
|
||||
|
||||
/**
|
||||
@@ -592,33 +669,31 @@ const TouchableMixin = {
|
||||
},
|
||||
|
||||
_handleQueryLayout: function(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
l: number,
|
||||
t: number,
|
||||
w: number,
|
||||
h: number,
|
||||
globalX: number,
|
||||
globalY: number
|
||||
) {
|
||||
// don't do anything if UIManager failed to measure node
|
||||
if (!x && !y && !width && !height && !globalX && !globalY) {
|
||||
//don't do anything UIManager failed to measure node
|
||||
if (!l && !t && !w && !h && !globalX && !globalY) {
|
||||
return;
|
||||
}
|
||||
this.state.touchable.positionOnActivate &&
|
||||
Position.release(this.state.touchable.positionOnActivate);
|
||||
this.state.touchable.dimensionsOnActivate &&
|
||||
// $FlowFixMe
|
||||
BoundingDimensions.release(this.state.touchable.dimensionsOnActivate);
|
||||
this.state.touchable.positionOnActivate = Position.getPooled(globalX, globalY);
|
||||
// $FlowFixMe
|
||||
this.state.touchable.dimensionsOnActivate = BoundingDimensions.getPooled(width, height);
|
||||
this.state.touchable.dimensionsOnActivate = BoundingDimensions.getPooled(w, h);
|
||||
},
|
||||
|
||||
_handleDelay: function(e: Event) {
|
||||
_handleDelay: function(e: PressEvent) {
|
||||
this.touchableDelayTimeout = null;
|
||||
this._receiveSignal(Signals.DELAY, e);
|
||||
},
|
||||
|
||||
_handleLongDelay: function(e: Event) {
|
||||
_handleLongDelay: function(e: PressEvent) {
|
||||
this.longPressDelayTimeout = null;
|
||||
const curState = this.state.touchable.touchState;
|
||||
if (
|
||||
@@ -646,7 +721,7 @@ const TouchableMixin = {
|
||||
* @throws Error if invalid state transition or unrecognized signal.
|
||||
* @sideeffects
|
||||
*/
|
||||
_receiveSignal: function(signal: string, e: Event) {
|
||||
_receiveSignal: function(signal: Signal, e: PressEvent) {
|
||||
const responderID = this.state.touchable.responderID;
|
||||
const curState = this.state.touchable.touchState;
|
||||
const nextState = Transitions[curState] && Transitions[curState][signal];
|
||||
@@ -686,26 +761,19 @@ const TouchableMixin = {
|
||||
this.longPressDelayTimeout = null;
|
||||
},
|
||||
|
||||
_isHighlight: function(state: string) {
|
||||
_isHighlight: function(state: State) {
|
||||
return (
|
||||
state === States.RESPONDER_ACTIVE_PRESS_IN || state === States.RESPONDER_ACTIVE_LONG_PRESS_IN
|
||||
);
|
||||
},
|
||||
|
||||
_savePressInLocation: function(e: Event) {
|
||||
const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
|
||||
_savePressInLocation: function(e: PressEvent) {
|
||||
const touch = extractSingleTouch(e.nativeEvent);
|
||||
const pageX = touch && touch.pageX;
|
||||
const pageY = touch && touch.pageY;
|
||||
this.pressInLocation = {
|
||||
pageX,
|
||||
pageY,
|
||||
get locationX() {
|
||||
return touch && touch.locationX;
|
||||
},
|
||||
get locationY() {
|
||||
return touch && touch.locationY;
|
||||
}
|
||||
};
|
||||
const locationX = touch && touch.locationX;
|
||||
const locationY = touch && touch.locationY;
|
||||
this.pressInLocation = { pageX, pageY, locationX, locationY };
|
||||
},
|
||||
|
||||
_getDistanceBetweenPoints: function(aX: number, aY: number, bX: number, bY: number) {
|
||||
@@ -726,10 +794,10 @@ const TouchableMixin = {
|
||||
* @sideeffects
|
||||
*/
|
||||
_performSideEffectsForTransition: function(
|
||||
curState: string,
|
||||
nextState: string,
|
||||
signal: string,
|
||||
e: Event
|
||||
curState: State,
|
||||
nextState: State,
|
||||
signal: Signal,
|
||||
e: PressEvent
|
||||
) {
|
||||
const curIsHighlight = this._isHighlight(curState);
|
||||
const newIsHighlight = this._isHighlight(nextState);
|
||||
@@ -741,7 +809,11 @@ const TouchableMixin = {
|
||||
this._cancelLongPressDelayTimeout();
|
||||
}
|
||||
|
||||
if (!IsActive[curState] && IsActive[nextState]) {
|
||||
const isInitialTransition =
|
||||
curState === States.NOT_RESPONDER && nextState === States.RESPONDER_INACTIVE_PRESS_IN;
|
||||
|
||||
const isActiveTransition = !IsActive[curState] && IsActive[nextState];
|
||||
if (isInitialTransition || isActiveTransition) {
|
||||
this._remeasureMetricsOnActivation();
|
||||
}
|
||||
|
||||
@@ -758,9 +830,8 @@ const TouchableMixin = {
|
||||
if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) {
|
||||
const hasLongPressHandler = !!this.props.onLongPress;
|
||||
const pressIsLongButStillCallOnPress =
|
||||
IsLongPressingIn[curState] && // We *are* long pressing..
|
||||
(!hasLongPressHandler || // But either has no long handler
|
||||
!this.touchableLongPressCancelsPress()); // or we're told to ignore it.
|
||||
IsLongPressingIn[curState] && // We *are* long pressing.. // But either has no long handler
|
||||
(!hasLongPressHandler || !this.touchableLongPressCancelsPress()); // or we're told to ignore it.
|
||||
|
||||
const shouldInvokePress = !IsLongPressingIn[curState] || pressIsLongButStillCallOnPress;
|
||||
if (shouldInvokePress && this.touchableHandlePress) {
|
||||
@@ -777,12 +848,16 @@ const TouchableMixin = {
|
||||
this.touchableDelayTimeout = null;
|
||||
},
|
||||
|
||||
_startHighlight: function(e: Event) {
|
||||
_playTouchSound: function() {
|
||||
UIManager.playTouchSound();
|
||||
},
|
||||
|
||||
_startHighlight: function(e: PressEvent) {
|
||||
this._savePressInLocation(e);
|
||||
this.touchableHandleActivePressIn && this.touchableHandleActivePressIn(e);
|
||||
},
|
||||
|
||||
_endHighlight: function(e: Event) {
|
||||
_endHighlight: function(e: PressEvent) {
|
||||
if (this.touchableHandleActivePressOut) {
|
||||
if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) {
|
||||
this.pressOutDelayTimeout = setTimeout(() => {
|
||||
@@ -797,10 +872,8 @@ const TouchableMixin = {
|
||||
// HACK (part 2): basic support for touchable interactions using a keyboard (including
|
||||
// delays and longPress)
|
||||
touchableHandleKeyEvent: function(e: Event) {
|
||||
const ENTER = 13;
|
||||
const SPACE = 32;
|
||||
const { type, which } = e;
|
||||
if (which === ENTER || which === SPACE) {
|
||||
const { type, key } = e;
|
||||
if (key === 'Enter' || key === ' ') {
|
||||
if (type === 'keydown') {
|
||||
if (!this._isTouchableKeyboardActive) {
|
||||
if (
|
||||
@@ -825,44 +898,66 @@ const TouchableMixin = {
|
||||
e.stopPropagation();
|
||||
// prevent the default behaviour unless the Touchable functions as a link
|
||||
// and Enter is pressed
|
||||
if (!(which === ENTER && AccessibilityUtil.propsToAriaRole(this.props) === 'link')) {
|
||||
if (!(key === 'Enter' && AccessibilityUtil.propsToAriaRole(this.props) === 'link')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
withoutDefaultFocusAndBlur: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide an optional version of the mixin where `touchableHandleFocus` and
|
||||
* `touchableHandleBlur` can be overridden. This allows appropriate defaults to
|
||||
* be set on TV platforms, without breaking existing implementations of
|
||||
* `Touchable`.
|
||||
*/
|
||||
const {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
touchableHandleFocus,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
touchableHandleBlur,
|
||||
...TouchableMixinWithoutDefaultFocusAndBlur
|
||||
} = TouchableMixin;
|
||||
TouchableMixin.withoutDefaultFocusAndBlur = TouchableMixinWithoutDefaultFocusAndBlur;
|
||||
|
||||
const Touchable = {
|
||||
Mixin: TouchableMixin,
|
||||
TOUCH_TARGET_DEBUG: false, // Highlights all touchable targets. Toggle with Inspector.
|
||||
/**
|
||||
* Renders a debugging overlay to visualize touch target with hitSlop (might not work on Android).
|
||||
*/
|
||||
renderDebugView: ({ color, hitSlop }: Object) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!Touchable.TOUCH_TARGET_DEBUG) {
|
||||
return null;
|
||||
}
|
||||
const debugHitSlopStyle = {};
|
||||
hitSlop = hitSlop || { top: 0, bottom: 0, left: 0, right: 0 };
|
||||
for (const key in hitSlop) {
|
||||
debugHitSlopStyle[key] = -hitSlop[key];
|
||||
}
|
||||
const hexColor = '#' + ('00000000' + normalizeColor(color).toString(16)).substr(-8);
|
||||
return (
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
borderColor: hexColor.slice(0, -2) + '55', // More opaque
|
||||
borderWidth: 1,
|
||||
borderStyle: 'dashed',
|
||||
backgroundColor: hexColor.slice(0, -2) + '0F', // Less opaque
|
||||
...debugHitSlopStyle
|
||||
}}
|
||||
/>
|
||||
);
|
||||
renderDebugView: ({ color, hitSlop }: { color: string | number, hitSlop: EdgeInsetsProp }) => {
|
||||
if (!Touchable.TOUCH_TARGET_DEBUG) {
|
||||
return null;
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
throw Error('Touchable.TOUCH_TARGET_DEBUG should not be enabled in prod!');
|
||||
}
|
||||
const debugHitSlopStyle = {};
|
||||
hitSlop = hitSlop || { top: 0, bottom: 0, left: 0, right: 0 };
|
||||
for (const key in hitSlop) {
|
||||
debugHitSlopStyle[key] = -hitSlop[key];
|
||||
}
|
||||
const normalizedColor = normalizeColor(color);
|
||||
if (typeof normalizedColor !== 'number') {
|
||||
return null;
|
||||
}
|
||||
const hexColor = '#' + ('00000000' + normalizedColor.toString(16)).substr(-8);
|
||||
return (
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
borderColor: hexColor.slice(0, -2) + '55', // More opaque
|
||||
borderWidth: 1,
|
||||
borderStyle: 'dashed',
|
||||
backgroundColor: hexColor.slice(0, -2) + '0F', // Less opaque
|
||||
...debugHitSlopStyle
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+262
-113
@@ -1,36 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) Nicolas Gallagher.
|
||||
* 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.
|
||||
*
|
||||
* @noflow
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import ColorPropType from '../ColorPropType';
|
||||
import DeprecatedColorPropType from '../ColorPropType';
|
||||
import createReactClass from 'create-react-class';
|
||||
import ensureComponentIsNative from '../../modules/ensureComponentIsNative';
|
||||
import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps';
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import TimerMixin from 'react-timer-mixin';
|
||||
import Touchable from '../Touchable';
|
||||
import TouchableWithoutFeedback from '../TouchableWithoutFeedback';
|
||||
import TouchableWithoutFeedback, {
|
||||
type Props as TouchableWithoutFeedbackProps
|
||||
} from '../TouchableWithoutFeedback';
|
||||
import View from '../View';
|
||||
import ViewPropTypes from '../ViewPropTypes';
|
||||
import { func, number } from 'prop-types';
|
||||
import DeprecatedViewPropTypes from '../ViewPropTypes';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
type Event = Object;
|
||||
type PressEvent = Object;
|
||||
|
||||
const DEFAULT_PROPS = {
|
||||
activeOpacity: 0.85,
|
||||
delayPressOut: 100,
|
||||
underlayColor: 'black'
|
||||
};
|
||||
|
||||
const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
...TouchableWithoutFeedbackProps,
|
||||
|
||||
activeOpacity?: ?number,
|
||||
underlayColor?: ?any,
|
||||
style?: ?any,
|
||||
onShowUnderlay?: ?() => void,
|
||||
onHideUnderlay?: ?() => void,
|
||||
testOnly_pressed?: ?boolean
|
||||
|}>;
|
||||
|
||||
/**
|
||||
* A wrapper for making views respond properly to touches.
|
||||
* On press down, the opacity of the wrapped view is decreased, which allows
|
||||
@@ -58,114 +72,244 @@ const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
|
||||
* );
|
||||
* },
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```ReactNativeWebPlayer
|
||||
* import React, { Component } from 'react'
|
||||
* import {
|
||||
* AppRegistry,
|
||||
* StyleSheet,
|
||||
* TouchableHighlight,
|
||||
* Text,
|
||||
* View,
|
||||
* } from 'react-native'
|
||||
*
|
||||
* class App extends Component {
|
||||
* constructor(props) {
|
||||
* super(props)
|
||||
* this.state = { count: 0 }
|
||||
* }
|
||||
*
|
||||
* onPress = () => {
|
||||
* this.setState({
|
||||
* count: this.state.count+1
|
||||
* })
|
||||
* }
|
||||
*
|
||||
* render() {
|
||||
* return (
|
||||
* <View style={styles.container}>
|
||||
* <TouchableHighlight
|
||||
* style={styles.button}
|
||||
* onPress={this.onPress}
|
||||
* >
|
||||
* <Text> Touch Here </Text>
|
||||
* </TouchableHighlight>
|
||||
* <View style={[styles.countContainer]}>
|
||||
* <Text style={[styles.countText]}>
|
||||
* { this.state.count !== 0 ? this.state.count: null}
|
||||
* </Text>
|
||||
* </View>
|
||||
* </View>
|
||||
* )
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* const styles = StyleSheet.create({
|
||||
* container: {
|
||||
* flex: 1,
|
||||
* justifyContent: 'center',
|
||||
* paddingHorizontal: 10
|
||||
* },
|
||||
* button: {
|
||||
* alignItems: 'center',
|
||||
* backgroundColor: '#DDDDDD',
|
||||
* padding: 10
|
||||
* },
|
||||
* countContainer: {
|
||||
* alignItems: 'center',
|
||||
* padding: 10
|
||||
* },
|
||||
* countText: {
|
||||
* color: '#FF00FF'
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* AppRegistry.registerComponent('App', () => App)
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
|
||||
/* eslint-disable react/prefer-es6-class */
|
||||
const TouchableHighlight = createReactClass({
|
||||
// eslint-disable-next-line react/prefer-es6-class
|
||||
const TouchableHighlight = ((createReactClass({
|
||||
displayName: 'TouchableHighlight',
|
||||
propTypes: {
|
||||
/* $FlowFixMe(>=0.89.0 site=react_native_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 what the opacity of the wrapped view should be when touch is
|
||||
* active.
|
||||
*/
|
||||
activeOpacity: number,
|
||||
/**
|
||||
* Called immediately after the underlay is hidden
|
||||
*/
|
||||
onHideUnderlay: func,
|
||||
/**
|
||||
* Called immediately after the underlay is shown
|
||||
*/
|
||||
onShowUnderlay: func,
|
||||
style: ViewPropTypes.style,
|
||||
activeOpacity: PropTypes.number,
|
||||
/**
|
||||
* The color of the underlay that will show through when the touch is
|
||||
* active.
|
||||
*/
|
||||
underlayColor: ColorPropType
|
||||
hasTVPreferredFocus: PropTypes.bool,
|
||||
/**
|
||||
* Style to apply to the container/underlay. Most commonly used to make sure
|
||||
* rounded corners match the wrapped component.
|
||||
*/
|
||||
nextFocusDown: PropTypes.number,
|
||||
/**
|
||||
* Called immediately after the underlay is shown
|
||||
*/
|
||||
nextFocusForward: PropTypes.number,
|
||||
/**
|
||||
* Called immediately after the underlay is hidden
|
||||
*/
|
||||
nextFocusLeft: PropTypes.number,
|
||||
/**
|
||||
* *(Apple TV only)* TV preferred focus (see documentation for the View component).
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
nextFocusRight: PropTypes.number,
|
||||
/**
|
||||
* TV next focus down (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
nextFocusUp: PropTypes.number,
|
||||
/**
|
||||
* TV next focus forward (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
onHideUnderlay: PropTypes.func,
|
||||
/**
|
||||
* TV next focus left (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
onShowUnderlay: PropTypes.func,
|
||||
/**
|
||||
* TV next focus right (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
style: DeprecatedViewPropTypes.style,
|
||||
/**
|
||||
* TV next focus up (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
testOnly_pressed: PropTypes.bool,
|
||||
/**
|
||||
* *(Apple TV only)* Object with properties to control Apple TV parallax effects.
|
||||
*
|
||||
* enabled: If true, parallax effects are enabled. Defaults to true.
|
||||
* shiftDistanceX: Defaults to 2.0.
|
||||
* shiftDistanceY: Defaults to 2.0.
|
||||
* tiltAngle: Defaults to 0.05.
|
||||
* magnification: Defaults to 1.0.
|
||||
* pressMagnification: Defaults to 1.0.
|
||||
* pressDuration: Defaults to 0.3.
|
||||
* pressDelay: Defaults to 0.0.
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
tvParallaxProperties: PropTypes.object,
|
||||
/**
|
||||
* Handy for snapshot tests.
|
||||
*/
|
||||
underlayColor: DeprecatedColorPropType
|
||||
},
|
||||
|
||||
mixins: [TimerMixin, Touchable.Mixin],
|
||||
mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur],
|
||||
|
||||
getDefaultProps: () => DEFAULT_PROPS,
|
||||
|
||||
// Performance optimization to avoid constantly re-generating these objects.
|
||||
_computeSyntheticState: function(props) {
|
||||
return {
|
||||
activeProps: {
|
||||
style: {
|
||||
opacity: props.activeOpacity
|
||||
}
|
||||
},
|
||||
activeUnderlayProps: {
|
||||
style: {
|
||||
backgroundColor: props.underlayColor
|
||||
}
|
||||
},
|
||||
underlayStyle: [INACTIVE_UNDERLAY_PROPS.style, props.style]
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
this._isMounted = false;
|
||||
return {
|
||||
...this.touchableGetInitialState(),
|
||||
...this._computeSyntheticState(this.props)
|
||||
};
|
||||
if (this.props.testOnly_pressed) {
|
||||
return {
|
||||
...this.touchableGetInitialState(),
|
||||
extraChildStyle: {
|
||||
opacity: this.props.activeOpacity
|
||||
},
|
||||
extraUnderlayStyle: {
|
||||
backgroundColor: this.props.underlayColor
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...this.touchableGetInitialState(),
|
||||
extraChildStyle: null,
|
||||
extraUnderlayStyle: null
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._isMounted = true;
|
||||
ensurePositiveDelayProps(this.props);
|
||||
ensureComponentIsNative(this._childRef);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._isMounted = false;
|
||||
clearTimeout(this._hideTimeout);
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
ensureComponentIsNative(this._childRef);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
ensurePositiveDelayProps(nextProps);
|
||||
if (
|
||||
nextProps.activeOpacity !== this.props.activeOpacity ||
|
||||
nextProps.underlayColor !== this.props.underlayColor ||
|
||||
nextProps.style !== this.props.style
|
||||
) {
|
||||
this.setState(this._computeSyntheticState(nextProps));
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
viewConfig: {
|
||||
uiViewClassName: 'RCTView',
|
||||
validAttributes: ReactNativeViewAttributes.RCTView,
|
||||
},
|
||||
*/
|
||||
|
||||
/**
|
||||
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
|
||||
* defined on your component.
|
||||
*/
|
||||
touchableHandleActivePressIn: function(e: Event) {
|
||||
this.clearTimeout(this._hideTimeout);
|
||||
touchableHandleActivePressIn: function(e: PressEvent) {
|
||||
clearTimeout(this._hideTimeout);
|
||||
this._hideTimeout = null;
|
||||
this._showUnderlay();
|
||||
this.props.onPressIn && this.props.onPressIn(e);
|
||||
},
|
||||
|
||||
touchableHandleActivePressOut: function(e: Event) {
|
||||
touchableHandleActivePressOut: function(e: PressEvent) {
|
||||
if (!this._hideTimeout) {
|
||||
this._hideUnderlay();
|
||||
}
|
||||
this.props.onPressOut && this.props.onPressOut(e);
|
||||
},
|
||||
|
||||
touchableHandlePress: function(e: Event) {
|
||||
this.clearTimeout(this._hideTimeout);
|
||||
touchableHandleFocus: function(e: Event) {
|
||||
this.props.onFocus && this.props.onFocus(e);
|
||||
},
|
||||
|
||||
touchableHandleBlur: function(e: Event) {
|
||||
this.props.onBlur && this.props.onBlur(e);
|
||||
},
|
||||
|
||||
touchableHandlePress: function(e: PressEvent) {
|
||||
clearTimeout(this._hideTimeout);
|
||||
this._showUnderlay();
|
||||
this._hideTimeout = this.setTimeout(this._hideUnderlay, this.props.delayPressOut || 100);
|
||||
this._hideTimeout = setTimeout(this._hideUnderlay, this.props.delayPressOut);
|
||||
this.props.onPress && this.props.onPress(e);
|
||||
},
|
||||
|
||||
touchableHandleLongPress: function(e: Event) {
|
||||
touchableHandleLongPress: function(e: PressEvent) {
|
||||
this.props.onLongPress && this.props.onLongPress(e);
|
||||
},
|
||||
|
||||
@@ -193,20 +337,27 @@ const TouchableHighlight = createReactClass({
|
||||
if (!this._isMounted || !this._hasPressHandler()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._underlayRef.setNativeProps(this.state.activeUnderlayProps);
|
||||
this._childRef.setNativeProps(this.state.activeProps);
|
||||
this.setState({
|
||||
extraChildStyle: {
|
||||
opacity: this.props.activeOpacity
|
||||
},
|
||||
extraUnderlayStyle: {
|
||||
backgroundColor: this.props.underlayColor
|
||||
}
|
||||
});
|
||||
this.props.onShowUnderlay && this.props.onShowUnderlay();
|
||||
},
|
||||
|
||||
_hideUnderlay: function() {
|
||||
this.clearTimeout(this._hideTimeout);
|
||||
clearTimeout(this._hideTimeout);
|
||||
this._hideTimeout = null;
|
||||
if (this._hasPressHandler() && this._underlayRef) {
|
||||
this._childRef.setNativeProps(INACTIVE_CHILD_PROPS);
|
||||
this._underlayRef.setNativeProps({
|
||||
...INACTIVE_UNDERLAY_PROPS,
|
||||
style: this.state.underlayStyle
|
||||
if (this.props.testOnly_pressed) {
|
||||
return;
|
||||
}
|
||||
if (this._hasPressHandler()) {
|
||||
this.setState({
|
||||
extraChildStyle: null,
|
||||
extraUnderlayStyle: null
|
||||
});
|
||||
this.props.onHideUnderlay && this.props.onHideUnderlay();
|
||||
}
|
||||
@@ -221,63 +372,61 @@ const TouchableHighlight = createReactClass({
|
||||
);
|
||||
},
|
||||
|
||||
_setChildRef(node) {
|
||||
this._childRef = node;
|
||||
},
|
||||
|
||||
_setUnderlayRef(node) {
|
||||
this._underlayRef = node;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const {
|
||||
/* eslint-disable */
|
||||
activeOpacity,
|
||||
onHideUnderlay,
|
||||
onShowUnderlay,
|
||||
underlayColor,
|
||||
delayLongPress,
|
||||
delayPressIn,
|
||||
delayPressOut,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
pressRetentionOffset,
|
||||
/* eslint-enable */
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
const child = React.Children.only(this.props.children);
|
||||
return (
|
||||
<View
|
||||
{...other}
|
||||
{...this.props}
|
||||
accessibilityHint={this.props.accessibilityHint}
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityRole={this.props.accessibilityRole}
|
||||
accessibilityState={this.props.accessibilityState}
|
||||
accessible={this.props.accessible !== false}
|
||||
hitSlop={this.props.hitSlop}
|
||||
nativeID={this.props.nativeID}
|
||||
onKeyDown={this.touchableHandleKeyEvent}
|
||||
//isTVSelectable={true}
|
||||
//tvParallaxProperties={this.props.tvParallaxProperties}
|
||||
//hasTVPreferredFocus={this.props.hasTVPreferredFocus}
|
||||
//nextFocusDown={this.props.nextFocusDown}
|
||||
//nextFocusForward={this.props.nextFocusForward}
|
||||
//nextFocusLeft={this.props.nextFocusLeft}
|
||||
//nextFocusRight={this.props.nextFocusRight}
|
||||
//nextFocusUp={this.props.nextFocusUp}
|
||||
//clickable={
|
||||
// this.props.clickable !== false && this.props.onPress !== undefined
|
||||
//}
|
||||
//onClick={this.touchableHandlePress}
|
||||
onKeyUp={this.touchableHandleKeyEvent}
|
||||
onLayout={this.props.onLayout}
|
||||
onResponderGrant={this.touchableHandleResponderGrant}
|
||||
onResponderMove={this.touchableHandleResponderMove}
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}
|
||||
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
ref={this._setUnderlayRef}
|
||||
style={[styles.root, !this.props.disabled && styles.actionable, this.state.underlayStyle]}
|
||||
style={[
|
||||
styles.root,
|
||||
this.props.style,
|
||||
!this.props.disabled && styles.actionable,
|
||||
this.state.extraUnderlayStyle
|
||||
]}
|
||||
testID={this.props.testID}
|
||||
>
|
||||
{React.cloneElement(React.Children.only(this.props.children), {
|
||||
ref: this._setChildRef
|
||||
{React.cloneElement(child, {
|
||||
style: StyleSheet.compose(
|
||||
child.props.style,
|
||||
this.state.extraChildStyle
|
||||
)
|
||||
})}
|
||||
{Touchable.renderDebugView({
|
||||
color: 'green',
|
||||
hitSlop: this.props.hitSlop
|
||||
})}
|
||||
{Touchable.renderDebugView({ color: 'green', hitSlop: this.props.hitSlop })}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const INACTIVE_CHILD_PROPS = {
|
||||
style: StyleSheet.create({ x: { opacity: 1.0 } }).x
|
||||
};
|
||||
const INACTIVE_UNDERLAY_PROPS = {
|
||||
style: StyleSheet.create({ x: { backgroundColor: 'transparent' } }).x
|
||||
};
|
||||
}): any): React.ComponentType<Props>);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
|
||||
+194
-47
@@ -1,35 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) Nicolas Gallagher.
|
||||
* 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.
|
||||
*
|
||||
* @noflow
|
||||
* @format
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import applyNativeMethods from '../../modules/applyNativeMethods';
|
||||
import createReactClass from 'create-react-class';
|
||||
import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps';
|
||||
import { number } from 'prop-types';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import Touchable from '../Touchable';
|
||||
import TouchableWithoutFeedback from '../TouchableWithoutFeedback';
|
||||
import TouchableWithoutFeedback, {
|
||||
type Props as TouchableWithoutFeedbackProps
|
||||
} from '../TouchableWithoutFeedback';
|
||||
import View from '../View';
|
||||
|
||||
const flattenStyle = StyleSheet.flatten;
|
||||
|
||||
type Event = Object;
|
||||
type PressEvent = Object;
|
||||
|
||||
const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
...TouchableWithoutFeedbackProps,
|
||||
activeOpacity?: ?number,
|
||||
style?: ?any
|
||||
|}>;
|
||||
|
||||
/**
|
||||
* A wrapper for making views respond properly to touches.
|
||||
* On press down, the opacity of the wrapped view is decreased, dimming it.
|
||||
*
|
||||
* Opacity is controlled by wrapping the children in a View, which is
|
||||
* added to the view hiearchy. Be aware that this can affect layout.
|
||||
* Opacity is controlled by wrapping the children in an Animated.View, which is
|
||||
* added to the view hiearchy. Be aware that this can affect layout.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
@@ -45,46 +56,160 @@ const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
|
||||
* );
|
||||
* },
|
||||
* ```
|
||||
* ### Example
|
||||
*
|
||||
* ```ReactNativeWebPlayer
|
||||
* import React, { Component } from 'react'
|
||||
* import {
|
||||
* AppRegistry,
|
||||
* StyleSheet,
|
||||
* TouchableOpacity,
|
||||
* Text,
|
||||
* View,
|
||||
* } from 'react-native'
|
||||
*
|
||||
* class App extends Component {
|
||||
* constructor(props) {
|
||||
* super(props)
|
||||
* this.state = { count: 0 }
|
||||
* }
|
||||
*
|
||||
* onPress = () => {
|
||||
* this.setState({
|
||||
* count: this.state.count+1
|
||||
* })
|
||||
* }
|
||||
*
|
||||
* render() {
|
||||
* return (
|
||||
* <View style={styles.container}>
|
||||
* <TouchableOpacity
|
||||
* style={styles.button}
|
||||
* onPress={this.onPress}
|
||||
* >
|
||||
* <Text> Touch Here </Text>
|
||||
* </TouchableOpacity>
|
||||
* <View style={[styles.countContainer]}>
|
||||
* <Text style={[styles.countText]}>
|
||||
* { this.state.count !== 0 ? this.state.count: null}
|
||||
* </Text>
|
||||
* </View>
|
||||
* </View>
|
||||
* )
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* const styles = StyleSheet.create({
|
||||
* container: {
|
||||
* flex: 1,
|
||||
* justifyContent: 'center',
|
||||
* paddingHorizontal: 10
|
||||
* },
|
||||
* button: {
|
||||
* alignItems: 'center',
|
||||
* backgroundColor: '#DDDDDD',
|
||||
* padding: 10
|
||||
* },
|
||||
* countContainer: {
|
||||
* alignItems: 'center',
|
||||
* padding: 10
|
||||
* },
|
||||
* countText: {
|
||||
* color: '#FF00FF'
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* AppRegistry.registerComponent('App', () => App)
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
|
||||
/* eslint-disable react/prefer-es6-class */
|
||||
const TouchableOpacity = createReactClass({
|
||||
// eslint-disable-next-line react/prefer-es6-class
|
||||
const TouchableOpacity = ((createReactClass({
|
||||
displayName: 'TouchableOpacity',
|
||||
mixins: [Touchable.Mixin],
|
||||
mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur],
|
||||
|
||||
propTypes: {
|
||||
/* $FlowFixMe(>=0.89.0 site=react_native_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 what the opacity of the wrapped view should be when touch is
|
||||
* active.
|
||||
* active. Defaults to 0.2.
|
||||
*/
|
||||
activeOpacity: number,
|
||||
focusedOpacity: number
|
||||
activeOpacity: PropTypes.number,
|
||||
/**
|
||||
* TV preferred focus (see documentation for the View component).
|
||||
*/
|
||||
hasTVPreferredFocus: PropTypes.bool,
|
||||
/**
|
||||
* TV next focus down (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
nextFocusDown: PropTypes.number,
|
||||
/**
|
||||
* TV next focus forward (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
nextFocusForward: PropTypes.number,
|
||||
/**
|
||||
* TV next focus left (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
nextFocusLeft: PropTypes.number,
|
||||
/**
|
||||
* TV next focus right (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
nextFocusRight: PropTypes.number,
|
||||
/**
|
||||
* TV next focus up (see documentation for the View component).
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
nextFocusUp: PropTypes.number,
|
||||
/**
|
||||
* Apple TV parallax effects
|
||||
*/
|
||||
tvParallaxProperties: PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
activeOpacity: 0.2,
|
||||
focusedOpacity: 0.7
|
||||
activeOpacity: 0.2
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return this.touchableGetInitialState();
|
||||
return {
|
||||
...this.touchableGetInitialState(),
|
||||
anim: this._getChildStyleOpacityWithDefault()
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
ensurePositiveDelayProps(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
||||
ensurePositiveDelayProps(nextProps);
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState) {
|
||||
if (this.props.disabled !== prevProps.disabled) {
|
||||
this._opacityInactive(250);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Animate the touchable to a new opacity.
|
||||
*/
|
||||
setOpacityTo: function(value: number, duration: ?number) {
|
||||
setOpacityTo: function(value: number, duration: number) {
|
||||
this.setNativeProps({
|
||||
style: {
|
||||
opacity: value,
|
||||
@@ -97,7 +222,7 @@ const TouchableOpacity = createReactClass({
|
||||
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
|
||||
* defined on your component.
|
||||
*/
|
||||
touchableHandleActivePressIn: function(e: Event) {
|
||||
touchableHandleActivePressIn: function(e: PressEvent) {
|
||||
if (e.dispatchConfig.registrationName === 'onResponderGrant') {
|
||||
this._opacityActive(0);
|
||||
} else {
|
||||
@@ -106,16 +231,30 @@ const TouchableOpacity = createReactClass({
|
||||
this.props.onPressIn && this.props.onPressIn(e);
|
||||
},
|
||||
|
||||
touchableHandleActivePressOut: function(e: Event) {
|
||||
touchableHandleActivePressOut: function(e: PressEvent) {
|
||||
this._opacityInactive(250);
|
||||
this.props.onPressOut && this.props.onPressOut(e);
|
||||
},
|
||||
|
||||
touchableHandlePress: function(e: Event) {
|
||||
touchableHandleFocus: function(e: Event) {
|
||||
//if (Platform.isTV) {
|
||||
// this._opacityActive(150);
|
||||
//}
|
||||
this.props.onFocus && this.props.onFocus(e);
|
||||
},
|
||||
|
||||
touchableHandleBlur: function(e: Event) {
|
||||
//if (Platform.isTV) {
|
||||
// this._opacityInactive(250);
|
||||
//}
|
||||
this.props.onBlur && this.props.onBlur(e);
|
||||
},
|
||||
|
||||
touchableHandlePress: function(e: PressEvent) {
|
||||
this.props.onPress && this.props.onPress(e);
|
||||
},
|
||||
|
||||
touchableHandleLongPress: function(e: Event) {
|
||||
touchableHandleLongPress: function(e: PressEvent) {
|
||||
this.props.onLongPress && this.props.onLongPress(e);
|
||||
},
|
||||
|
||||
@@ -147,52 +286,60 @@ const TouchableOpacity = createReactClass({
|
||||
this.setOpacityTo(this._getChildStyleOpacityWithDefault(), duration);
|
||||
},
|
||||
|
||||
_opacityFocused: function() {
|
||||
this.setOpacityTo(this.props.focusedOpacity);
|
||||
},
|
||||
|
||||
_getChildStyleOpacityWithDefault: function() {
|
||||
const childStyle = flattenStyle(this.props.style) || {};
|
||||
return childStyle.opacity === undefined ? 1 : childStyle.opacity;
|
||||
return childStyle.opacity == null ? 1 : childStyle.opacity;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const {
|
||||
/* eslint-disable */
|
||||
activeOpacity,
|
||||
focusedOpacity,
|
||||
delayLongPress,
|
||||
delayPressIn,
|
||||
delayPressOut,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
pressRetentionOffset,
|
||||
/* eslint-enable */
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
{...other}
|
||||
{...this.props}
|
||||
accessibilityHint={this.props.accessibilityHint}
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityRole={this.props.accessibilityRole}
|
||||
accessibilityState={this.props.accessibilityState}
|
||||
accessible={this.props.accessible !== false}
|
||||
hitSlop={this.props.hitSlop}
|
||||
nativeID={this.props.nativeID}
|
||||
onKeyDown={this.touchableHandleKeyEvent}
|
||||
onKeyUp={this.touchableHandleKeyEvent}
|
||||
onLayout={this.props.onLayout}
|
||||
onResponderGrant={this.touchableHandleResponderGrant}
|
||||
//isTVSelectable={true}
|
||||
//nextFocusDown={this.props.nextFocusDown}
|
||||
//nextFocusForward={this.props.nextFocusForward}
|
||||
//nextFocusLeft={this.props.nextFocusLeft}
|
||||
//nextFocusRight={this.props.nextFocusRight}
|
||||
//nextFocusUp={this.props.nextFocusUp}
|
||||
//hasTVPreferredFocus={this.props.hasTVPreferredFocus}
|
||||
//tvParallaxProperties={this.props.tvParallaxProperties}
|
||||
onResponderMove={this.touchableHandleResponderMove}
|
||||
//clickable={
|
||||
// this.props.clickable !== false && this.props.onPress !== undefined
|
||||
//}
|
||||
//onClick={this.touchableHandlePress}
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}
|
||||
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
style={[styles.root, !this.props.disabled && styles.actionable, this.props.style]}
|
||||
style={[
|
||||
styles.root,
|
||||
!this.props.disabled && styles.actionable,
|
||||
this.props.style,
|
||||
{ opacity: this.state.anim }
|
||||
]}
|
||||
testID={this.props.testID}
|
||||
>
|
||||
{this.props.children}
|
||||
{Touchable.renderDebugView({ color: 'blue', hitSlop: this.props.hitSlop })}
|
||||
{Touchable.renderDebugView({
|
||||
color: 'cyan',
|
||||
hitSlop: this.props.hitSlop
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
}): any): React.ComponentType<Props>);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
|
||||
@@ -1,28 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Nicolas Gallagher.
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* 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
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import createReactClass from 'create-react-class';
|
||||
import EdgeInsetsPropType from '../EdgeInsetsPropType';
|
||||
import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps';
|
||||
import React, { type Element } from 'react';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import TimerMixin from 'react-timer-mixin';
|
||||
import Touchable from '../Touchable';
|
||||
import ViewPropTypes from '../ViewPropTypes';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import { any, bool, func, number, string } from 'prop-types';
|
||||
'use strict';
|
||||
|
||||
type Event = Object;
|
||||
import createReactClass from 'create-react-class';
|
||||
import DeprecatedEdgeInsetsPropType from '../EdgeInsetsPropType';
|
||||
import ensurePositiveDelayProps from '../Touchable/ensurePositiveDelayProps';
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Touchable from '../Touchable';
|
||||
import View from '../View';
|
||||
|
||||
type BlurEvent = Object;
|
||||
type FocusEvent = Object;
|
||||
type PressEvent = Object;
|
||||
type LayoutEvent = Object;
|
||||
type EdgeInsetsProp = Object;
|
||||
|
||||
const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
|
||||
|
||||
const OVERRIDE_PROPS = [
|
||||
'accessibilityLabel',
|
||||
'accessibilityHint',
|
||||
'accessibilityIgnoresInvertColors',
|
||||
'accessibilityRole',
|
||||
'accessibilityState',
|
||||
'hitSlop',
|
||||
'nativeID',
|
||||
'onBlur',
|
||||
'onFocus',
|
||||
'onLayout',
|
||||
'testID'
|
||||
];
|
||||
|
||||
export type Props = $ReadOnly<{|
|
||||
accessible?: ?boolean,
|
||||
accessibilityLabel?: ?string,
|
||||
accessibilityHint?: ?string,
|
||||
accessibilityIgnoresInvertColors?: ?boolean,
|
||||
accessibilityRole?: ?string,
|
||||
accessibilityState?: ?Object,
|
||||
children?: ?React.Node,
|
||||
delayLongPress?: ?number,
|
||||
delayPressIn?: ?number,
|
||||
delayPressOut?: ?number,
|
||||
disabled?: ?boolean,
|
||||
hitSlop?: ?EdgeInsetsProp,
|
||||
nativeID?: ?string,
|
||||
touchSoundDisabled?: ?boolean,
|
||||
onBlur?: ?(e: BlurEvent) => void,
|
||||
onFocus?: ?(e: FocusEvent) => void,
|
||||
onLayout?: ?(event: LayoutEvent) => mixed,
|
||||
onLongPress?: ?(event: PressEvent) => mixed,
|
||||
onPress?: ?(event: PressEvent) => mixed,
|
||||
onPressIn?: ?(event: PressEvent) => mixed,
|
||||
onPressOut?: ?(event: PressEvent) => mixed,
|
||||
pressRetentionOffset?: ?EdgeInsetsProp,
|
||||
rejectResponderTermination?: ?boolean,
|
||||
testID?: ?string
|
||||
|}>;
|
||||
|
||||
/**
|
||||
* Do not use unless you have a very good reason. All elements that
|
||||
* respond to press should have a visual feedback when touched.
|
||||
@@ -30,53 +74,76 @@ const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };
|
||||
* TouchableWithoutFeedback supports only one child.
|
||||
* If you wish to have several child components, wrap them in a View.
|
||||
*/
|
||||
|
||||
/* eslint-disable react/prefer-es6-class, react/prop-types */
|
||||
const TouchableWithoutFeedback = createReactClass({
|
||||
// eslint-disable-next-line react/prefer-es6-class
|
||||
const TouchableWithoutFeedback = ((createReactClass({
|
||||
displayName: 'TouchableWithoutFeedback',
|
||||
mixins: [TimerMixin, Touchable.Mixin],
|
||||
mixins: [Touchable.Mixin],
|
||||
|
||||
propTypes: {
|
||||
accessibilityLabel: string,
|
||||
accessibilityRole: ViewPropTypes.accessibilityRole,
|
||||
accessible: bool,
|
||||
children: any,
|
||||
accessibilityHint: PropTypes.string,
|
||||
accessibilityIgnoresInvertColors: PropTypes.bool,
|
||||
accessibilityLabel: PropTypes.node,
|
||||
accessibilityRole: PropTypes.string,
|
||||
accessibilityState: PropTypes.object,
|
||||
accessible: PropTypes.bool,
|
||||
/**
|
||||
* Delay in ms, from onPressIn, before onLongPress is called.
|
||||
* When `accessible` is true (which is the default) this may be called when
|
||||
* the OS-specific concept of "focus" occurs. Some platforms may not have
|
||||
* the concept of focus.
|
||||
*/
|
||||
delayLongPress: number,
|
||||
delayLongPress: PropTypes.number,
|
||||
/**
|
||||
* Delay in ms, from the start of the touch, before onPressIn is called.
|
||||
* When `accessible` is true (which is the default) this may be called when
|
||||
* the OS-specific concept of "blur" occurs, meaning the element lost focus.
|
||||
* Some platforms may not have the concept of blur.
|
||||
*/
|
||||
delayPressIn: number,
|
||||
/**
|
||||
* Delay in ms, from the release of the touch, before onPressOut is called.
|
||||
*/
|
||||
delayPressOut: number,
|
||||
delayPressIn: PropTypes.number,
|
||||
/**
|
||||
* If true, disable all interactions for this component.
|
||||
*/
|
||||
disabled: bool,
|
||||
delayPressOut: PropTypes.number,
|
||||
/**
|
||||
* This defines how far your touch can start away from the button. This is
|
||||
* added to `pressRetentionOffset` when moving off of the button.
|
||||
* Called when the touch is released, but not if cancelled (e.g. by a scroll
|
||||
* that steals the responder lock).
|
||||
*/
|
||||
// $FlowFixMe(>=0.41.0)
|
||||
hitSlop: EdgeInsetsPropType,
|
||||
disabled: PropTypes.bool,
|
||||
/**
|
||||
* Called as soon as the touchable element is pressed and invoked even before onPress.
|
||||
* This can be useful when making network requests.
|
||||
*/
|
||||
hitSlop: DeprecatedEdgeInsetsPropType,
|
||||
/**
|
||||
* Called as soon as the touch is released even before onPress.
|
||||
*/
|
||||
nativeID: PropTypes.string,
|
||||
/**
|
||||
* Invoked on mount and layout changes with
|
||||
*
|
||||
* `{nativeEvent: {layout: {x, y, width, height}}}`
|
||||
*/
|
||||
onLayout: func,
|
||||
onLongPress: func,
|
||||
onBlur: PropTypes.func,
|
||||
/**
|
||||
* Called when the touch is released, but not if cancelled (e.g. by a scroll
|
||||
* that steals the responder lock).
|
||||
* If true, doesn't play system sound on touch (Android Only)
|
||||
**/
|
||||
onFocus: PropTypes.func,
|
||||
|
||||
onLayout: PropTypes.func,
|
||||
|
||||
onLongPress: PropTypes.func,
|
||||
onPress: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Delay in ms, from the start of the touch, before onPressIn is called.
|
||||
*/
|
||||
onPress: func,
|
||||
onPressIn: func,
|
||||
onPressOut: func,
|
||||
onPressIn: PropTypes.func,
|
||||
/**
|
||||
* Delay in ms, from the release of the touch, before onPressOut is called.
|
||||
*/
|
||||
onPressOut: PropTypes.func,
|
||||
/**
|
||||
* Delay in ms, from onPressIn, before onLongPress is called.
|
||||
*/
|
||||
pressRetentionOffset: DeprecatedEdgeInsetsPropType,
|
||||
/**
|
||||
* When the scroll view is disabled, this defines how far your touch may
|
||||
* move off of the button, before deactivating the button. Once deactivated,
|
||||
@@ -84,9 +151,16 @@ const TouchableWithoutFeedback = createReactClass({
|
||||
* reactivated! Move it back and forth several times while the scroll view
|
||||
* is disabled. Ensure you pass in a constant to reduce memory allocations.
|
||||
*/
|
||||
// $FlowFixMe
|
||||
pressRetentionOffset: EdgeInsetsPropType,
|
||||
testID: string
|
||||
testID: PropTypes.string,
|
||||
/**
|
||||
* This defines how far your touch can start away from the button. This is
|
||||
* added to `pressRetentionOffset` when moving off of the button.
|
||||
* ** NOTE **
|
||||
* The touch area never extends past the parent view bounds and the Z-index
|
||||
* of sibling views always takes precedence if a touch hits two overlapping
|
||||
* views.
|
||||
*/
|
||||
touchSoundDisabled: PropTypes.bool
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@@ -97,7 +171,7 @@ const TouchableWithoutFeedback = createReactClass({
|
||||
ensurePositiveDelayProps(this.props);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps: Object) {
|
||||
UNSAFE_componentWillReceiveProps: function(nextProps: Object) {
|
||||
ensurePositiveDelayProps(nextProps);
|
||||
},
|
||||
|
||||
@@ -105,23 +179,24 @@ const TouchableWithoutFeedback = createReactClass({
|
||||
* `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
|
||||
* defined on your component.
|
||||
*/
|
||||
touchableHandlePress: function(e: Event) {
|
||||
touchableHandlePress: function(e: PressEvent) {
|
||||
this.props.onPress && this.props.onPress(e);
|
||||
},
|
||||
|
||||
touchableHandleActivePressIn: function(e: Event) {
|
||||
touchableHandleActivePressIn: function(e: PressEvent) {
|
||||
this.props.onPressIn && this.props.onPressIn(e);
|
||||
},
|
||||
|
||||
touchableHandleActivePressOut: function(e: Event) {
|
||||
touchableHandleActivePressOut: function(e: PressEvent) {
|
||||
this.props.onPressOut && this.props.onPressOut(e);
|
||||
},
|
||||
|
||||
touchableHandleLongPress: function(e: Event) {
|
||||
touchableHandleLongPress: function(e: PressEvent) {
|
||||
this.props.onLongPress && this.props.onLongPress(e);
|
||||
},
|
||||
|
||||
touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
|
||||
// $FlowFixMe Invalid prop usage
|
||||
return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
|
||||
},
|
||||
|
||||
@@ -141,65 +216,41 @@ const TouchableWithoutFeedback = createReactClass({
|
||||
return this.props.delayPressOut || 0;
|
||||
},
|
||||
|
||||
render: function(): Element<any> {
|
||||
const {
|
||||
/* eslint-disable */
|
||||
delayLongPress,
|
||||
delayPressIn,
|
||||
delayPressOut,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
pressRetentionOffset,
|
||||
/* eslint-enable */
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
render: function(): React.Element<any> {
|
||||
// Note(avik): remove dynamic typecast once Flow has been upgraded
|
||||
// $FlowFixMe
|
||||
// $FlowFixMe(>=0.41.0)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const child = React.Children.only(this.props.children);
|
||||
let children = child.props.children;
|
||||
warning(
|
||||
!child.type || child.type.displayName !== 'Text',
|
||||
'TouchableWithoutFeedback does not work well with Text children. Wrap children in a View instead. See ' +
|
||||
((child._owner && child._owner.getName && child._owner.getName()) || '<unknown>')
|
||||
);
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
Touchable.TOUCH_TARGET_DEBUG &&
|
||||
child.type &&
|
||||
child.type.displayName === 'View'
|
||||
) {
|
||||
if (Touchable.TOUCH_TARGET_DEBUG && child.type === View) {
|
||||
children = React.Children.toArray(children);
|
||||
children.push(Touchable.renderDebugView({ color: 'red', hitSlop: this.props.hitSlop }));
|
||||
}
|
||||
const style =
|
||||
Touchable.TOUCH_TARGET_DEBUG && child.type && child.type.displayName === 'Text'
|
||||
? [!this.props.disabled && styles.actionable, child.props.style, { color: 'red' }]
|
||||
: [!this.props.disabled && styles.actionable, child.props.style];
|
||||
|
||||
const overrides = {};
|
||||
for (const prop of OVERRIDE_PROPS) {
|
||||
if (this.props[prop] !== undefined) {
|
||||
overrides[prop] = this.props[prop];
|
||||
}
|
||||
}
|
||||
|
||||
return (React: any).cloneElement(child, {
|
||||
...other,
|
||||
...overrides,
|
||||
accessible: this.props.accessible !== false,
|
||||
children,
|
||||
//clickable:
|
||||
// this.props.clickable !== false && this.props.onPress !== undefined,
|
||||
//onClick: this.touchableHandlePress,
|
||||
onKeyDown: this.touchableHandleKeyEvent,
|
||||
onKeyUp: this.touchableHandleKeyEvent,
|
||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
||||
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
|
||||
onResponderGrant: this.touchableHandleResponderGrant,
|
||||
onResponderMove: this.touchableHandleResponderMove,
|
||||
onResponderRelease: this.touchableHandleResponderRelease,
|
||||
onResponderTerminate: this.touchableHandleResponderTerminate,
|
||||
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
|
||||
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
|
||||
style
|
||||
children
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
actionable: {
|
||||
cursor: 'pointer',
|
||||
touchAction: 'manipulation'
|
||||
}
|
||||
});
|
||||
}): any): React.ComponentType<Props>);
|
||||
|
||||
export default TouchableWithoutFeedback;
|
||||
|
||||
Reference in New Issue
Block a user