TouchableWithoutFeedback.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @flow strict-local
  8. * @format
  9. */
  10. 'use strict';
  11. import Pressability, {
  12. type PressabilityConfig,
  13. } from '../../Pressability/Pressability';
  14. import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
  15. import TVTouchable from './TVTouchable';
  16. import type {
  17. AccessibilityActionEvent,
  18. AccessibilityActionInfo,
  19. AccessibilityRole,
  20. AccessibilityState,
  21. AccessibilityValue,
  22. } from '../../Components/View/ViewAccessibility';
  23. import type {EdgeInsetsProp} from '../../StyleSheet/EdgeInsetsPropType';
  24. import type {
  25. BlurEvent,
  26. FocusEvent,
  27. LayoutEvent,
  28. PressEvent,
  29. } from '../../Types/CoreEventTypes';
  30. import Platform from '../../Utilities/Platform';
  31. import View from '../../Components/View/View';
  32. import * as React from 'react';
  33. type Props = $ReadOnly<{|
  34. accessibilityActions?: ?$ReadOnlyArray<AccessibilityActionInfo>,
  35. accessibilityElementsHidden?: ?boolean,
  36. accessibilityHint?: ?Stringish,
  37. accessibilityIgnoresInvertColors?: ?boolean,
  38. accessibilityLabel?: ?Stringish,
  39. accessibilityLiveRegion?: ?('none' | 'polite' | 'assertive'),
  40. accessibilityRole?: ?AccessibilityRole,
  41. accessibilityState?: ?AccessibilityState,
  42. accessibilityValue?: ?AccessibilityValue,
  43. accessibilityViewIsModal?: ?boolean,
  44. accessible?: ?boolean,
  45. children?: ?React.Node,
  46. delayLongPress?: ?number,
  47. delayPressIn?: ?number,
  48. delayPressOut?: ?number,
  49. disabled?: ?boolean,
  50. focusable?: ?boolean,
  51. hitSlop?: ?EdgeInsetsProp,
  52. importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'),
  53. nativeID?: ?string,
  54. onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed,
  55. onBlur?: ?(event: BlurEvent) => mixed,
  56. onFocus?: ?(event: FocusEvent) => mixed,
  57. onLayout?: ?(event: LayoutEvent) => mixed,
  58. onLongPress?: ?(event: PressEvent) => mixed,
  59. onPress?: ?(event: PressEvent) => mixed,
  60. onPressIn?: ?(event: PressEvent) => mixed,
  61. onPressOut?: ?(event: PressEvent) => mixed,
  62. pressRetentionOffset?: ?EdgeInsetsProp,
  63. rejectResponderTermination?: ?boolean,
  64. testID?: ?string,
  65. touchSoundDisabled?: ?boolean,
  66. |}>;
  67. type State = $ReadOnly<{|
  68. pressability: Pressability,
  69. |}>;
  70. const PASSTHROUGH_PROPS = [
  71. 'accessibilityActions',
  72. 'accessibilityElementsHidden',
  73. 'accessibilityHint',
  74. 'accessibilityIgnoresInvertColors',
  75. 'accessibilityLabel',
  76. 'accessibilityLiveRegion',
  77. 'accessibilityRole',
  78. 'accessibilityState',
  79. 'accessibilityValue',
  80. 'accessibilityViewIsModal',
  81. 'hitSlop',
  82. 'importantForAccessibility',
  83. 'nativeID',
  84. 'onAccessibilityAction',
  85. 'onBlur',
  86. 'onFocus',
  87. 'onLayout',
  88. 'testID',
  89. ];
  90. class TouchableWithoutFeedback extends React.Component<Props, State> {
  91. _tvTouchable: ?TVTouchable;
  92. state: State = {
  93. pressability: new Pressability(createPressabilityConfig(this.props)),
  94. };
  95. render(): React.Node {
  96. const element = React.Children.only(this.props.children);
  97. const children = [element.props.children];
  98. if (__DEV__) {
  99. if (element.type === View) {
  100. children.push(
  101. <PressabilityDebugView color="red" hitSlop={this.props.hitSlop} />,
  102. );
  103. }
  104. }
  105. // BACKWARD-COMPATIBILITY: Focus and blur events were never supported before
  106. // adopting `Pressability`, so preserve that behavior.
  107. const {
  108. onBlur,
  109. onFocus,
  110. ...eventHandlersWithoutBlurAndFocus
  111. } = this.state.pressability.getEventHandlers();
  112. const elementProps: {[string]: mixed, ...} = {
  113. ...eventHandlersWithoutBlurAndFocus,
  114. accessible: this.props.accessible !== false,
  115. focusable:
  116. this.props.focusable !== false && this.props.onPress !== undefined,
  117. };
  118. for (const prop of PASSTHROUGH_PROPS) {
  119. if (this.props[prop] !== undefined) {
  120. elementProps[prop] = this.props[prop];
  121. }
  122. }
  123. return React.cloneElement(element, elementProps, ...children);
  124. }
  125. componentDidMount(): void {
  126. if (Platform.isTV) {
  127. this._tvTouchable = new TVTouchable(this, {
  128. getDisabled: () => this.props.disabled === true,
  129. onBlur: event => {
  130. if (this.props.onBlur != null) {
  131. this.props.onBlur(event);
  132. }
  133. },
  134. onFocus: event => {
  135. if (this.props.onFocus != null) {
  136. this.props.onFocus(event);
  137. }
  138. },
  139. onPress: event => {
  140. if (this.props.onPress != null) {
  141. this.props.onPress(event);
  142. }
  143. },
  144. });
  145. }
  146. }
  147. componentDidUpdate(): void {
  148. this.state.pressability.configure(createPressabilityConfig(this.props));
  149. }
  150. componentWillUnmount(): void {
  151. if (Platform.isTV) {
  152. if (this._tvTouchable != null) {
  153. this._tvTouchable.destroy();
  154. }
  155. }
  156. this.state.pressability.reset();
  157. }
  158. }
  159. function createPressabilityConfig(props: Props): PressabilityConfig {
  160. return {
  161. cancelable: !props.rejectResponderTermination,
  162. disabled: props.disabled,
  163. hitSlop: props.hitSlop,
  164. delayLongPress: props.delayLongPress,
  165. delayPressIn: props.delayPressIn,
  166. delayPressOut: props.delayPressOut,
  167. minPressDuration: 0,
  168. pressRectOffset: props.pressRetentionOffset,
  169. android_disableSound: props.touchSoundDisabled,
  170. onBlur: props.onBlur,
  171. onFocus: props.onFocus,
  172. onLongPress: props.onLongPress,
  173. onPress: props.onPress,
  174. onPressIn: props.onPressIn,
  175. onPressOut: props.onPressOut,
  176. };
  177. }
  178. module.exports = TouchableWithoutFeedback;