diff --git a/docs/storybook/1-components/Touchable/TouchableHighlightDocs.js b/docs/storybook/1-components/Touchable/TouchableHighlightDocs.js index 8ed915db..ef540d26 100644 --- a/docs/storybook/1-components/Touchable/TouchableHighlightDocs.js +++ b/docs/storybook/1-components/Touchable/TouchableHighlightDocs.js @@ -5,6 +5,8 @@ */ import CustomStyleOverrides from './examples/CustomStyleOverrides'; +import DelayEvents from './examples/DelayEvents'; +import FeedbackEvents from './examples/FeedbackEvents'; import React from 'react'; import { storiesOf } from '@kadira/storybook'; import { TouchableHighlightDisabled } from './examples/PropDisabled'; @@ -45,13 +47,27 @@ const sections = [ title: 'More examples', entries: [ }} />, + + }} + />, + + + }} + />, + }} + />, + + + }} + />, + + + }} /> ] } diff --git a/docs/storybook/1-components/Touchable/TouchableWithoutFeedbackDocs.js b/docs/storybook/1-components/Touchable/TouchableWithoutFeedbackDocs.js index c4ec9417..e79acedd 100644 --- a/docs/storybook/1-components/Touchable/TouchableWithoutFeedbackDocs.js +++ b/docs/storybook/1-components/Touchable/TouchableWithoutFeedbackDocs.js @@ -4,9 +4,12 @@ * @flow */ +import DelayEvents from './examples/DelayEvents'; +import FeedbackEvents from './examples/FeedbackEvents'; import React from 'react'; import PropHitSlop from './examples/PropHitSlop'; import { storiesOf } from '@kadira/storybook'; +import { TouchableWithoutFeedbackDisabled } from './examples/PropDisabled'; import UIExplorer, { AppText, Code, DocItem } from '../../ui-explorer'; const sections = [ @@ -53,6 +56,9 @@ const sections = [ If true, disable all interactions for this component. } + example={{ + render: () => + }} />, , @@ -83,7 +89,23 @@ constant to reduce memory allocations.`} { title: 'More examples', - entries: [ }} />] + entries: [ + + }} + />, + + + }} + />, + + }} /> + ] } ]; diff --git a/docs/storybook/1-components/Touchable/examples/DelayEvents.js b/docs/storybook/1-components/Touchable/examples/DelayEvents.js new file mode 100644 index 00000000..61a3c9ad --- /dev/null +++ b/docs/storybook/1-components/Touchable/examples/DelayEvents.js @@ -0,0 +1,101 @@ +/** + * @flow + */ + +import { oneOf } from 'prop-types'; +import React, { PureComponent } from 'react'; +import { + StyleSheet, + Text, + TouchableHighlight, + TouchableOpacity, + TouchableWithoutFeedback, + View +} from 'react-native'; + +const Touchables = { + highlight: TouchableHighlight, + opacity: TouchableOpacity, + withoutFeedback: TouchableWithoutFeedback +}; + +export default class TouchableDelayEvents extends PureComponent { + static propTypes = { + touchable: oneOf(['highlight', 'opacity', 'withoutFeedback']) + }; + + static defaultProps = { + touchable: 'highlight' + }; + + state = { eventLog: [] }; + + render() { + const Touchable = Touchables[this.props.touchable]; + const { displayName } = Touchable; + return ( + + + + + {displayName} + + + + + {this.state.eventLog.map((e, ii) => + + {e} + + )} + + + ); + } + + _createPressHandler = eventName => { + return () => { + const limit = 6; + this.setState(state => { + const eventLog = state.eventLog.slice(0, limit - 1); + eventLog.unshift(eventName); + return { eventLog }; + }); + }; + }; +} + +const styles = StyleSheet.create({ + touchableText: { + borderRadius: 8, + padding: 5, + borderWidth: 1, + borderColor: 'black', + color: '#007AFF', + borderStyle: 'solid', + textAlign: 'center' + }, + logBox: { + padding: 20, + margin: 10, + borderWidth: StyleSheet.hairlineWidth, + borderColor: '#f0f0f0', + backgroundColor: '#f9f9f9' + }, + eventLogBox: { + padding: 10, + margin: 10, + height: 120, + borderWidth: StyleSheet.hairlineWidth, + borderColor: '#f0f0f0', + backgroundColor: '#f9f9f9' + } +}); diff --git a/docs/storybook/1-components/Touchable/examples/FeedbackEvents.js b/docs/storybook/1-components/Touchable/examples/FeedbackEvents.js new file mode 100644 index 00000000..46f98dbc --- /dev/null +++ b/docs/storybook/1-components/Touchable/examples/FeedbackEvents.js @@ -0,0 +1,95 @@ +/** + * @flow + */ + +import { oneOf } from 'prop-types'; +import React, { PureComponent } from 'react'; +import { + StyleSheet, + Text, + TouchableHighlight, + TouchableOpacity, + TouchableWithoutFeedback, + View +} from 'react-native'; + +const Touchables = { + highlight: TouchableHighlight, + opacity: TouchableOpacity, + withoutFeedback: TouchableWithoutFeedback +}; + +export default class TouchableFeedbackEvents extends PureComponent { + static propTypes = { + touchable: oneOf(['highlight', 'opacity', 'withoutFeedback']) + }; + + static defaultProps = { + touchable: 'highlight' + }; + + state = { eventLog: [] }; + + render() { + const Touchable = Touchables[this.props.touchable]; + return ( + + + + Press Me + + + + {this.state.eventLog.map((e, ii) => + + {e} + + )} + + + ); + } + + _createPressHandler = eventName => { + return () => { + const limit = 6; + this.setState(state => { + const eventLog = state.eventLog.slice(0, limit - 1); + eventLog.unshift(eventName); + return { eventLog }; + }); + }; + }; +} + +const styles = StyleSheet.create({ + touchableText: { + borderRadius: 8, + padding: 5, + borderWidth: 1, + borderColor: 'black', + color: '#007AFF', + borderStyle: 'solid', + textAlign: 'center' + }, + logBox: { + padding: 20, + margin: 10, + borderWidth: StyleSheet.hairlineWidth, + borderColor: '#f0f0f0', + backgroundColor: '#f9f9f9' + }, + eventLogBox: { + padding: 10, + margin: 10, + height: 120, + borderWidth: StyleSheet.hairlineWidth, + borderColor: '#f0f0f0', + backgroundColor: '#f9f9f9' + } +}); diff --git a/docs/storybook/1-components/Touchable/examples/PropDisabled.js b/docs/storybook/1-components/Touchable/examples/PropDisabled.js index 12e3ce52..8f3c0a87 100644 --- a/docs/storybook/1-components/Touchable/examples/PropDisabled.js +++ b/docs/storybook/1-components/Touchable/examples/PropDisabled.js @@ -4,7 +4,14 @@ import React from 'react'; import { action } from '@kadira/storybook'; -import { StyleSheet, View, Text, TouchableHighlight, TouchableOpacity } from 'react-native'; +import { + StyleSheet, + View, + Text, + TouchableHighlight, + TouchableOpacity, + TouchableWithoutFeedback +} from 'react-native'; class TouchableHighlightDisabled extends React.Component { render() { @@ -57,7 +64,27 @@ class TouchableOpacityDisabled extends React.Component { } } -export { TouchableHighlightDisabled, TouchableOpacityDisabled }; +class TouchableWithoutFeedbackDisabled extends React.Component { + render() { + return ( + + + + Disabled TouchableWithoutFeedback + + + + + + Enabled TouchableWithoutFeedback + + + + ); + } +} + +export { TouchableHighlightDisabled, TouchableOpacityDisabled, TouchableWithoutFeedbackDisabled }; const styles = StyleSheet.create({ row: { @@ -66,12 +93,5 @@ const styles = StyleSheet.create({ }, block: { padding: 10 - }, - button: { - color: '#007AFF' - }, - disabledButton: { - color: '#007AFF', - opacity: 0.5 } }); diff --git a/docs/storybook/1-components/Touchable/examples/legacy.js b/docs/storybook/1-components/Touchable/examples/legacy.js index 85bace26..759b35af 100644 --- a/docs/storybook/1-components/Touchable/examples/legacy.js +++ b/docs/storybook/1-components/Touchable/examples/legacy.js @@ -17,92 +17,6 @@ import { View } from 'react-native'; -class TouchableFeedbackEvents extends React.Component { - constructor(props) { - super(props); - this.state = { eventLog: [] }; - } - - render() { - return ( - - - - - Press Me - - - - - {this.state.eventLog.map((e, ii) => {e})} - - - ); - } - - _createPressHandler = eventName => { - return () => { - const limit = 6; - const eventLog = this.state.eventLog.slice(0, limit - 1); - eventLog.unshift(eventName); - this.setState({ eventLog }); - }; - }; -} - -class TouchableDelayEvents extends React.Component { - constructor(props) { - super(props); - this.state = { eventLog: [] }; - } - - render() { - return ( - - - - - Press Me - - - - - {this.state.eventLog.map((e, ii) => {e})} - - - ); - } - - _createPressHandler = eventName => { - return () => { - const limit = 6; - const eventLog = this.state.eventLog.slice(0, limit - 1); - eventLog.unshift(eventName); - this.setState({ eventLog }); - }; - }; -} - const heartImage = { uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small' }; const styles = StyleSheet.create({ @@ -214,33 +128,5 @@ const examples = [ ); } }, - { - title: 'Touchable feedback events', - description: - ' components accept onPress, onPressIn, ' + - 'onPressOut, and onLongPress as props.', - render() { - return ; - } - }, - { - title: 'Touchable delay for events', - description: - ' components also accept delayPressIn, ' + - 'delayPressOut, and delayLongPress as props. These props impact the ' + - 'timing of feedback events.', - render() { - return ; - } - }, - { - title: 'Disabled Touchable*', - description: - ' components accept disabled prop which prevents ' + - 'any interaction with component', - render() { - return ; - } - } ]; */ diff --git a/src/components/TextInput/index.js b/src/components/TextInput/index.js index 44beeef2..95e7943d 100644 --- a/src/components/TextInput/index.js +++ b/src/components/TextInput/index.js @@ -14,7 +14,6 @@ import applyLayout from '../../modules/applyLayout'; import applyNativeMethods from '../../modules/applyNativeMethods'; import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; import { Component } from 'react'; -import NativeMethodsMixin from '../../modules/NativeMethodsMixin'; import createDOMElement from '../../modules/createDOMElement'; import findNodeHandle from '../../modules/findNodeHandle'; import StyleSheet from '../../apis/StyleSheet'; @@ -145,10 +144,6 @@ class TextInput extends Component { return TextInputState.currentlyFocusedField() === this._node; } - setNativeProps(props) { - NativeMethodsMixin.setNativeProps.call(this, props); - } - componentDidMount() { setSelection(this._node, this.props.selection); } diff --git a/src/components/Touchable/Touchable.js b/src/components/Touchable/Touchable.js index f975de84..90af32ef 100644 --- a/src/components/Touchable/Touchable.js +++ b/src/components/Touchable/Touchable.js @@ -1,4 +1,4 @@ -/* eslint-disable */ +/* eslint-disable react/prop-types */ /** * Copyright (c) 2016-present, Nicolas Gallagher. @@ -9,18 +9,19 @@ * LICENSE file in the root directory of this source tree. * * @providesModule Touchable - * @noflow + * @flow */ -/* @edit start */ import BoundingDimensions from './BoundingDimensions'; +import findNodeHandle from '../../modules/findNodeHandle'; import normalizeColor from 'normalize-css-color'; import Position from './Position'; import React from 'react'; import TouchEventUtils from 'fbjs/lib/TouchEventUtils'; import UIManager from '../../apis/UIManager'; import View from '../../components/View'; -/* @edit end */ + +type Event = Object; /** * `Touchable`: Taps done right. @@ -314,10 +315,27 @@ const LONG_PRESS_ALLOWED_MOVEMENT = 10; * @lends Touchable.prototype */ const TouchableMixin = { + componentDidMount: function() { + this._touchableNode = findNodeHandle(this); + this._touchableBlurListener = e => { + if (this._isTouchableKeyboardActive) { + if ( + this.state.touchable.touchState && + this.state.touchable.touchState !== States.NOT_RESPONDER + ) { + this.touchableHandleResponderTerminate({ nativeEvent: e }); + } + this._isTouchableKeyboardActive = false; + } + }; + this._touchableNode.addEventListener('blur', this._touchableBlurListener); + }, + /** * Clear all timeouts on unmount */ componentWillUnmount: function() { + this._touchableNode.removeEventListener('blur', this._touchableBlurListener); this.touchableDelayTimeout && clearTimeout(this.touchableDelayTimeout); this.longPressDelayTimeout && clearTimeout(this.longPressDelayTimeout); this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout); @@ -360,16 +378,13 @@ const TouchableMixin = { /** * Place as callback for a DOM element's `onResponderGrant` event. - * @param {SyntheticEvent} e Synthetic event from event system. - * */ - touchableHandleResponderGrant: function(e) { + touchableHandleResponderGrant: function(e: Event) { 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; @@ -401,7 +416,7 @@ const TouchableMixin = { /** * Place as callback for a DOM element's `onResponderRelease` event. */ - touchableHandleResponderRelease: function(e) { + touchableHandleResponderRelease: function(e: Event) { this._receiveSignal(Signals.RESPONDER_RELEASE, e); // Browsers fire mouse events after touch events. This causes the // 'onResponderRelease' handler to be called twice for Touchables. @@ -415,14 +430,14 @@ const TouchableMixin = { /** * Place as callback for a DOM element's `onResponderTerminate` event. */ - touchableHandleResponderTerminate: function(e) { + touchableHandleResponderTerminate: function(e: Event) { this._receiveSignal(Signals.RESPONDER_TERMINATED, e); }, /** * Place as callback for a DOM element's `onResponderMove` event. */ - touchableHandleResponderMove: function(e) { + 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) { @@ -578,21 +593,34 @@ const TouchableMixin = { UIManager.measure(tag, this._handleQueryLayout); }, - _handleQueryLayout: function(l, t, w, h, globalX, globalY) { + _handleQueryLayout: function( + x: number, + y: number, + width: number, + height: number, + globalX: number, + globalY: number + ) { + // don't do anything if UIManager failed to measure node + if (!x && !y && !width && !height && !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); - this.state.touchable.dimensionsOnActivate = BoundingDimensions.getPooled(w, h); + // $FlowFixMe + this.state.touchable.dimensionsOnActivate = BoundingDimensions.getPooled(width, height); }, - _handleDelay: function(e) { + _handleDelay: function(e: Event) { this.touchableDelayTimeout = null; this._receiveSignal(Signals.DELAY, e); }, - _handleLongDelay: function(e) { + _handleLongDelay: function(e: Event) { this.longPressDelayTimeout = null; const curState = this.state.touchable.touchState; if ( @@ -620,7 +648,7 @@ const TouchableMixin = { * @throws Error if invalid state transition or unrecognized signal. * @sideeffects */ - _receiveSignal: function(signal, e) { + _receiveSignal: function(signal: string, e: Event) { const responderID = this.state.touchable.responderID; const curState = this.state.touchable.touchState; const nextState = Transitions[curState] && Transitions[curState][signal]; @@ -660,13 +688,13 @@ const TouchableMixin = { this.longPressDelayTimeout = null; }, - _isHighlight: function(state) { + _isHighlight: function(state: string) { return ( state === States.RESPONDER_ACTIVE_PRESS_IN || state === States.RESPONDER_ACTIVE_LONG_PRESS_IN ); }, - _savePressInLocation: function(e) { + _savePressInLocation: function(e: Event) { const touch = TouchEventUtils.extractSingleTouch(e.nativeEvent); const pageX = touch && touch.pageX; const pageY = touch && touch.pageY; @@ -675,7 +703,7 @@ const TouchableMixin = { this.pressInLocation = { pageX, pageY, locationX, locationY }; }, - _getDistanceBetweenPoints: function(aX, aY, bX, bY) { + _getDistanceBetweenPoints: function(aX: number, aY: number, bX: number, bY: number) { const deltaX = aX - bX; const deltaY = aY - bY; return Math.sqrt(deltaX * deltaX + deltaY * deltaY); @@ -692,7 +720,12 @@ const TouchableMixin = { * @param {Event} e Native event. * @sideeffects */ - _performSideEffectsForTransition: function(curState, nextState, signal, e) { + _performSideEffectsForTransition: function( + curState: string, + nextState: string, + signal: string, + e: Event + ) { const curIsHighlight = this._isHighlight(curState); const newIsHighlight = this._isHighlight(nextState); @@ -739,12 +772,12 @@ const TouchableMixin = { this.touchableDelayTimeout = null; }, - _startHighlight: function(e) { + _startHighlight: function(e: Event) { this._savePressInLocation(e); this.touchableHandleActivePressIn && this.touchableHandleActivePressIn(e); }, - _endHighlight: function(e) { + _endHighlight: function(e: Event) { if (this.touchableHandleActivePressOut) { if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) { this.pressOutDelayTimeout = setTimeout(() => { @@ -754,29 +787,59 @@ const TouchableMixin = { this.touchableHandleActivePressOut(e); } } + }, + + // HACK: 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) { + if (type === 'keydown') { + if (!this._isTouchableKeyboardActive) { + if ( + !this.state.touchable.touchState || + this.state.touchable.touchState === States.NOT_RESPONDER + ) { + this.touchableHandleResponderGrant(e); + this._isTouchableKeyboardActive = true; + } + } + } else if (type === 'keyup') { + if (this._isTouchableKeyboardActive) { + if ( + this.state.touchable.touchState && + this.state.touchable.touchState !== States.NOT_RESPONDER + ) { + this.touchableHandleResponderRelease(e); + this._isTouchableKeyboardActive = false; + } + } + } + e.stopPropagation(); + e.preventDefault(); + } } }; -var Touchable = { +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 }) => { + 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 ( * * * ); * }, * ``` - * > **NOTE**: TouchableHighlight must have one child (not zero or more than one) - * > - * > If you wish to have several child components, wrap them in a View. */ +/* eslint-disable react/prefer-es6-class */ const TouchableHighlight = createReactClass({ + displayName: 'TouchableHighlight', propTypes: { ...TouchableWithoutFeedback.propTypes, /** @@ -72,37 +73,36 @@ const TouchableHighlight = createReactClass({ */ activeOpacity: number, /** - * The color of the underlay that will show through when the touch is - * active. + * Called immediately after the underlay is hidden */ - underlayColor: ColorPropType, - style: StyleSheetPropType(ViewStylePropTypes), + onHideUnderlay: func, /** * Called immediately after the underlay is shown */ onShowUnderlay: func, + style: ViewPropTypes.style, /** - * Called immediately after the underlay is hidden + * The color of the underlay that will show through when the touch is + * active. */ - onHideUnderlay: func + underlayColor: ColorPropType }, - mixins: [NativeMethodsMixin, TimerMixin, Touchable.Mixin], + mixins: [TimerMixin, Touchable.Mixin], getDefaultProps: () => DEFAULT_PROPS, // Performance optimization to avoid constantly re-generating these objects. - computeSyntheticState: props => { - const { activeOpacity, style, underlayColor } = props; + _computeSyntheticState: function(props) { return { activeProps: { style: { - opacity: activeOpacity + opacity: props.activeOpacity } }, activeUnderlayProps: { style: { - backgroundColor: underlayColor + backgroundColor: props.underlayColor } }, underlayStyle: [INACTIVE_UNDERLAY_PROPS.style, props.style] @@ -110,20 +110,25 @@ const TouchableHighlight = createReactClass({ }, getInitialState: function() { + this._isMounted = false; return { ...this.touchableGetInitialState(), - ...this.computeSyntheticState(this.props) + ...this._computeSyntheticState(this.props) }; }, componentDidMount: function() { - ensurePositiveDelayProps(this.props); - ensureComponentIsNative(this.refs[CHILD_REF]); this._isMounted = true; + ensurePositiveDelayProps(this.props); + ensureComponentIsNative(this._childRef); + }, + + componentWillUnmount: function() { + this._isMounted = false; }, componentDidUpdate: function() { - ensureComponentIsNative(this.refs[CHILD_REF]); + ensureComponentIsNative(this._childRef); }, componentWillReceiveProps: function(nextProps) { @@ -133,19 +138,10 @@ const TouchableHighlight = createReactClass({ nextProps.underlayColor !== this.props.underlayColor || nextProps.style !== this.props.style ) { - this.setState(this.computeSyntheticState(nextProps)); + this.setState(this._computeSyntheticState(nextProps)); } }, - componentWillUnmount: function() { - this._isMounted = false; - }, - - // viewConfig: { - // uiViewClassName: 'RCTView', - // validAttributes: ReactNativeViewAttributes.RCTView - // }, - /** * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are * defined on your component. @@ -200,17 +196,17 @@ const TouchableHighlight = createReactClass({ return; } - this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps); - this.refs[CHILD_REF].setNativeProps(this.state.activeProps); + this._underlayRef.setNativeProps(this.state.activeUnderlayProps); + this._childRef.setNativeProps(this.state.activeProps); this.props.onShowUnderlay && this.props.onShowUnderlay(); }, _hideUnderlay: function() { this.clearTimeout(this._hideTimeout); this._hideTimeout = null; - if (this._hasPressHandler() && this.refs[UNDERLAY_REF]) { - this.refs[CHILD_REF].setNativeProps(INACTIVE_CHILD_PROPS); - this.refs[UNDERLAY_REF].setNativeProps({ + if (this._hasPressHandler() && this._underlayRef) { + this._childRef.setNativeProps(INACTIVE_CHILD_PROPS); + this._underlayRef.setNativeProps({ ...INACTIVE_UNDERLAY_PROPS, style: this.state.underlayStyle }); @@ -227,12 +223,12 @@ const TouchableHighlight = createReactClass({ ); }, - _onKeyEnter(e, callback) { - const ENTER = 13; - if ((e.type === 'keypress' ? e.charCode : e.keyCode) === ENTER) { - callback && callback(e); - e.stopPropagation(); - } + _setChildRef(node) { + this._childRef = node; + }, + + _setUnderlayRef(node) { + this._underlayRef = node; }, render: function() { @@ -258,26 +254,19 @@ const TouchableHighlight = createReactClass({ { - this._onKeyEnter(e, this.touchableHandleActivePressIn); - }} - onKeyPress={e => { - this._onKeyEnter(e, this.touchableHandlePress); - }} - onKeyUp={e => { - this._onKeyEnter(e, this.touchableHandleActivePressOut); - }} - onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} - onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest} + onKeyDown={this.touchableHandleKeyEvent} + onKeyUp={this.touchableHandleKeyEvent} onResponderGrant={this.touchableHandleResponderGrant} onResponderMove={this.touchableHandleResponderMove} onResponderRelease={this.touchableHandleResponderRelease} onResponderTerminate={this.touchableHandleResponderTerminate} - ref={UNDERLAY_REF} + onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest} + onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} + ref={this._setUnderlayRef} style={[styles.root, this.props.disabled && styles.disabled, this.state.underlayStyle]} > {React.cloneElement(React.Children.only(this.props.children), { - ref: CHILD_REF + ref: this._setChildRef })} {Touchable.renderDebugView({ color: 'green', hitSlop: this.props.hitSlop })} @@ -285,17 +274,14 @@ const TouchableHighlight = createReactClass({ } }); -var CHILD_REF = 'childRef'; -var UNDERLAY_REF = 'underlayRef'; - -var INACTIVE_CHILD_PROPS = { +const INACTIVE_CHILD_PROPS = { style: StyleSheet.create({ x: { opacity: 1.0 } }).x }; -var INACTIVE_UNDERLAY_PROPS = { +const INACTIVE_UNDERLAY_PROPS = { style: StyleSheet.create({ x: { backgroundColor: 'transparent' } }).x }; -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ root: { cursor: 'pointer', userSelect: 'none' @@ -305,4 +291,4 @@ var styles = StyleSheet.create({ } }); -export default TouchableHighlight; +export default applyNativeMethods(TouchableHighlight); diff --git a/src/components/Touchable/TouchableNativeFeedback.js b/src/components/Touchable/TouchableNativeFeedback.js index 0a818b1a..ff1004e2 100644 --- a/src/components/Touchable/TouchableNativeFeedback.js +++ b/src/components/Touchable/TouchableNativeFeedback.js @@ -1,2 +1,12 @@ +/** + * Copyright 2017-present, Nicolas Gallagher + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + import UnimplementedView from '../UnimplementedView'; export default UnimplementedView; diff --git a/src/components/Touchable/TouchableOpacity.js b/src/components/Touchable/TouchableOpacity.js index 82f33a02..70f77165 100644 --- a/src/components/Touchable/TouchableOpacity.js +++ b/src/components/Touchable/TouchableOpacity.js @@ -1,5 +1,3 @@ -/* eslint-disable */ - /** * Copyright (c) 2016-present, Nicolas Gallagher. * Copyright (c) 2015-present, Facebook, Inc. @@ -12,13 +10,12 @@ * @noflow */ +import applyNativeMethods from '../../modules/applyNativeMethods'; import createReactClass from 'create-react-class'; import ensurePositiveDelayProps from './ensurePositiveDelayProps'; -import NativeMethodsMixin from '../../modules/NativeMethodsMixin'; import { number } from 'prop-types'; import React from 'react'; import StyleSheet from '../../apis/StyleSheet'; -import TimerMixin from 'react-timer-mixin'; import Touchable from './Touchable'; import TouchableWithoutFeedback from './TouchableWithoutFeedback'; import View from '../View'; @@ -32,8 +29,9 @@ const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 }; /** * A wrapper for making views respond properly to touches. * On press down, the opacity of the wrapped view is decreased, dimming it. - * This is done without actually changing the view hierarchy, and in general is - * easy to add to an app without weird side-effects. + * + * Opacity is controlled by wrapping the children in a View, which is + * added to the view hiearchy. Be aware that this can affect layout. * * Example: * @@ -43,15 +41,18 @@ const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 }; * * * * ); * }, * ``` */ + +/* eslint-disable react/prefer-es6-class */ const TouchableOpacity = createReactClass({ - mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin], + displayName: 'TouchableOpacity', + mixins: [Touchable.Mixin], propTypes: { ...TouchableWithoutFeedback.propTypes, @@ -82,11 +83,14 @@ const TouchableOpacity = createReactClass({ ensurePositiveDelayProps(nextProps); }, - setOpacityTo: function(value: number, duration: number) { + /** + * Animate the touchable to a new opacity. + */ + setOpacityTo: function(value: number, duration: ?number) { this.setNativeProps({ style: { opacity: value, - transitionDuration: `${duration / 1000}s` + transitionDuration: duration ? `${duration / 1000}s` : '0s' } }); }, @@ -142,20 +146,16 @@ const TouchableOpacity = createReactClass({ }, _opacityInactive: function(duration: number) { - const childStyle = flattenStyle(this.props.style) || {}; - this.setOpacityTo(childStyle.opacity === undefined ? 1 : childStyle.opacity, duration); + this.setOpacityTo(this._getChildStyleOpacityWithDefault(), duration); }, _opacityFocused: function() { this.setOpacityTo(this.props.focusedOpacity); }, - _onKeyEnter(e, callback) { - const ENTER = 13; - if ((e.type === 'keypress' ? e.charCode : e.keyCode) === ENTER) { - callback && callback(e); - e.stopPropagation(); - } + _getChildStyleOpacityWithDefault: function() { + const childStyle = flattenStyle(this.props.style) || {}; + return childStyle.opacity === undefined ? 1 : childStyle.opacity; }, render: function() { @@ -179,22 +179,15 @@ const TouchableOpacity = createReactClass({ { - this._onKeyEnter(e, this.touchableHandleActivePressIn); - }} - onKeyPress={e => { - this._onKeyEnter(e, this.touchableHandlePress); - }} - onKeyUp={e => { - this._onKeyEnter(e, this.touchableHandleActivePressOut); - }} - onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} - onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest} + onKeyDown={this.touchableHandleKeyEvent} + onKeyUp={this.touchableHandleKeyEvent} onResponderGrant={this.touchableHandleResponderGrant} onResponderMove={this.touchableHandleResponderMove} onResponderRelease={this.touchableHandleResponderRelease} onResponderTerminate={this.touchableHandleResponderTerminate} + onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest} + onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} + style={[styles.root, this.props.disabled && styles.disabled, this.props.style]} > {this.props.children} {Touchable.renderDebugView({ color: 'blue', hitSlop: this.props.hitSlop })} @@ -203,7 +196,7 @@ const TouchableOpacity = createReactClass({ } }); -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ root: { cursor: 'pointer', transitionProperty: 'opacity', @@ -215,4 +208,4 @@ var styles = StyleSheet.create({ } }); -export default TouchableOpacity; +export default applyNativeMethods(TouchableOpacity); diff --git a/src/components/Touchable/TouchableWithoutFeedback.js b/src/components/Touchable/TouchableWithoutFeedback.js index 873acfae..88e45d2f 100644 --- a/src/components/Touchable/TouchableWithoutFeedback.js +++ b/src/components/Touchable/TouchableWithoutFeedback.js @@ -1,5 +1,3 @@ -/* eslint-disable */ - /** * Copyright (c) 2016-present, Nicolas Gallagher. * Copyright (c) 2015-present, Facebook, Inc. @@ -9,9 +7,10 @@ * LICENSE file in the root directory of this source tree. * * @providesModule TouchableWithoutFeedback - * @noflow + * @flow */ +import BaseComponentPropTypes from '../../propTypes/BaseComponentPropTypes'; import createReactClass from 'create-react-class'; import EdgeInsetsPropType from '../../propTypes/EdgeInsetsPropType'; import ensurePositiveDelayProps from './ensurePositiveDelayProps'; @@ -27,42 +26,29 @@ type Event = Object; const PRESS_RETENTION_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 }; /** - * Do not use unless you have a very good reason. All the elements that - * respond to press should have a visual feedback when touched. This is - * one of the primary reasons a "web" app doesn't feel "native". + * Do not use unless you have a very good reason. All elements that + * respond to press should have a visual feedback when touched. * - * > **NOTE**: TouchableWithoutFeedback supports only one child - * > - * > If you wish to have several child components, wrap them in a View. + * TouchableWithoutFeedback supports only one child. + * If you wish to have several child components, wrap them in a View. */ + +/* eslint-disable react/prefer-es6-class */ const TouchableWithoutFeedback = createReactClass({ + displayName: 'TouchableWithoutFeedback', mixins: [TimerMixin, Touchable.Mixin], propTypes: { - accessible: bool, + accessibilityComponentType: BaseComponentPropTypes.accessibilityComponentType, accessibilityLabel: string, - accessibilityRole: string, + accessibilityRole: BaseComponentPropTypes.accessibilityRole, + accessibilityTraits: BaseComponentPropTypes.accessibilityTraits, + accessible: bool, children: element, /** - * If true, disable all interactions for this component. + * Delay in ms, from onPressIn, before onLongPress is called. */ - disabled: bool, - /** - * Called when the touch is released, but not if cancelled (e.g. by a scroll - * that steals the responder lock). - */ - onPress: func, - onPressIn: func, - onPressOut: func, - /** - * Invoked on mount and layout changes with - * - * `{nativeEvent: {layout: {x, y, width, height}}}` - */ - onLayout: func, - - onLongPress: func, - + delayLongPress: number, /** * Delay in ms, from the start of the touch, before onPressIn is called. */ @@ -72,9 +58,29 @@ const TouchableWithoutFeedback = createReactClass({ */ delayPressOut: number, /** - * Delay in ms, from onPressIn, before onLongPress is called. + * If true, disable all interactions for this component. */ - delayLongPress: number, + disabled: bool, + /** + * This defines how far your touch can start away from the button. This is + * added to `pressRetentionOffset` when moving off of the button. + */ + // $FlowFixMe(>=0.41.0) + hitSlop: EdgeInsetsPropType, + /** + * Invoked on mount and layout changes with + * + * `{nativeEvent: {layout: {x, y, width, height}}}` + */ + onLayout: func, + onLongPress: func, + /** + * Called when the touch is released, but not if cancelled (e.g. by a scroll + * that steals the responder lock). + */ + onPress: func, + onPressIn: func, + onPressOut: func, /** * 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,16 +90,7 @@ const TouchableWithoutFeedback = createReactClass({ */ // $FlowFixMe pressRetentionOffset: EdgeInsetsPropType, - /** - * 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. - */ - // $FlowFixMe - hitSlop: EdgeInsetsPropType + testID: string }, getInitialState: function() { @@ -187,19 +184,19 @@ const TouchableWithoutFeedback = createReactClass({ return (React: any).cloneElement(child, { ...other, accessible: this.props.accessible !== false, - onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, - onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest, + children, onResponderGrant: this.touchableHandleResponderGrant, onResponderMove: this.touchableHandleResponderMove, onResponderRelease: this.touchableHandleResponderRelease, onResponderTerminate: this.touchableHandleResponderTerminate, - style, - children + onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest, + onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, + style }); } }); -var styles = StyleSheet.create({ +const styles = StyleSheet.create({ root: { cursor: 'pointer' },