/** * Copyright (c) 2015-present, Facebook, Inc. * * 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'; const React = require('React'); const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); const TextAncestor = require('TextAncestor'); const TextPropTypes = require('TextPropTypes'); const Touchable = require('Touchable'); const UIManager = require('UIManager'); const createReactNativeComponentClass = require('createReactNativeComponentClass'); const nullthrows = require('fbjs/lib/nullthrows'); const processColor = require('processColor'); import type {PressEvent} from 'CoreEventTypes'; import type {NativeComponent} from 'ReactNative'; import type {PressRetentionOffset, TextProps} from 'TextProps'; type ResponseHandlers = $ReadOnly<{| onStartShouldSetResponder: () => boolean, onResponderGrant: (event: SyntheticEvent<>, dispatchID: string) => void, onResponderMove: (event: SyntheticEvent<>) => void, onResponderRelease: (event: SyntheticEvent<>) => void, onResponderTerminate: (event: SyntheticEvent<>) => void, onResponderTerminationRequest: () => boolean, |}>; type Props = $ReadOnly<{ ...TextProps, forwardedRef: ?React.Ref<'RCTText' | 'RCTVirtualText'>, }>; type State = {| touchable: {| touchState: ?string, responderID: ?number, |}, isHighlighted: boolean, createResponderHandlers: () => ResponseHandlers, responseHandlers: ?ResponseHandlers, |}; const PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; const viewConfig = { validAttributes: { ...ReactNativeViewAttributes.UIView, isHighlighted: true, numberOfLines: true, ellipsizeMode: true, allowFontScaling: true, disabled: true, selectable: true, selectionColor: true, adjustsFontSizeToFit: true, minimumFontScale: true, textBreakStrategy: true, onTextLayout: true, }, directEventTypes: { topTextLayout: { registrationName: 'onTextLayout', }, }, uiViewClassName: 'RCTText', }; /** * A React component for displaying text. * * See https://facebook.github.io/react-native/docs/text.html */ class TouchableText extends React.Component { static defaultProps = { accessible: true, allowFontScaling: true, ellipsizeMode: 'tail', }; touchableGetPressRectOffset: ?() => PressRetentionOffset; touchableHandleActivePressIn: ?() => void; touchableHandleActivePressOut: ?() => void; touchableHandleLongPress: ?(event: PressEvent) => void; touchableHandlePress: ?(event: PressEvent) => void; touchableHandleResponderGrant: ?( event: SyntheticEvent<>, dispatchID: string, ) => void; touchableHandleResponderMove: ?(event: SyntheticEvent<>) => void; touchableHandleResponderRelease: ?(event: SyntheticEvent<>) => void; touchableHandleResponderTerminate: ?(event: SyntheticEvent<>) => void; touchableHandleResponderTerminationRequest: ?() => boolean; state = { ...Touchable.Mixin.touchableGetInitialState(), isHighlighted: false, createResponderHandlers: this._createResponseHandlers.bind(this), responseHandlers: null, }; static getDerivedStateFromProps(nextProps: Props, prevState: State): ?State { return prevState.responseHandlers == null && isTouchable(nextProps) ? { ...prevState, responseHandlers: prevState.createResponderHandlers(), } : null; } static viewConfig = viewConfig; render(): React.Node { let props = this.props; if (isTouchable(props)) { props = { ...props, ...this.state.responseHandlers, isHighlighted: this.state.isHighlighted, }; } if (props.selectionColor != null) { props = { ...props, selectionColor: processColor(props.selectionColor), }; } if (__DEV__) { if (Touchable.TOUCH_TARGET_DEBUG && props.onPress != null) { props = { ...props, style: [props.style, {color: 'magenta'}], }; } } return ( {hasTextAncestor => hasTextAncestor ? ( ) : ( ) } ); } _createResponseHandlers(): ResponseHandlers { return { onStartShouldSetResponder: (): boolean => { const {onStartShouldSetResponder} = this.props; const shouldSetResponder = (onStartShouldSetResponder == null ? false : onStartShouldSetResponder()) || isTouchable(this.props); if (shouldSetResponder) { this._attachTouchHandlers(); } return shouldSetResponder; }, onResponderGrant: (event: SyntheticEvent<>, dispatchID: string): void => { nullthrows(this.touchableHandleResponderGrant)(event, dispatchID); if (this.props.onResponderGrant != null) { this.props.onResponderGrant.call(this, event, dispatchID); } }, onResponderMove: (event: SyntheticEvent<>): void => { nullthrows(this.touchableHandleResponderMove)(event); if (this.props.onResponderMove != null) { this.props.onResponderMove.call(this, event); } }, onResponderRelease: (event: SyntheticEvent<>): void => { nullthrows(this.touchableHandleResponderRelease)(event); if (this.props.onResponderRelease != null) { this.props.onResponderRelease.call(this, event); } }, onResponderTerminate: (event: SyntheticEvent<>): void => { nullthrows(this.touchableHandleResponderTerminate)(event); if (this.props.onResponderTerminate != null) { this.props.onResponderTerminate.call(this, event); } }, onResponderTerminationRequest: (): boolean => { const {onResponderTerminationRequest} = this.props; if (!nullthrows(this.touchableHandleResponderTerminationRequest)()) { return false; } if (onResponderTerminationRequest == null) { return true; } return onResponderTerminationRequest(); }, }; } /** * Lazily attaches Touchable.Mixin handlers. */ _attachTouchHandlers(): void { if (this.touchableGetPressRectOffset != null) { return; } for (const key in Touchable.Mixin) { if (typeof Touchable.Mixin[key] === 'function') { (this: any)[key] = Touchable.Mixin[key].bind(this); } } this.touchableHandleActivePressIn = (): void => { if (!this.props.suppressHighlighting && isTouchable(this.props)) { this.setState({isHighlighted: true}); } }; this.touchableHandleActivePressOut = (): void => { if (!this.props.suppressHighlighting && isTouchable(this.props)) { this.setState({isHighlighted: false}); } }; this.touchableHandlePress = (event: PressEvent): void => { if (this.props.onPress != null) { this.props.onPress(event); } }; this.touchableHandleLongPress = (event: PressEvent): void => { if (this.props.onLongPress != null) { this.props.onLongPress(event); } }; this.touchableGetPressRectOffset = (): PressRetentionOffset => this.props.pressRetentionOffset == null ? PRESS_RECT_OFFSET : this.props.pressRetentionOffset; } } const isTouchable = (props: Props): boolean => props.onPress != null || props.onLongPress != null || props.onStartShouldSetResponder != null; const RCTText = createReactNativeComponentClass( viewConfig.uiViewClassName, () => viewConfig, ); const RCTVirtualText = UIManager.RCTVirtualText == null ? RCTText : createReactNativeComponentClass('RCTVirtualText', () => ({ validAttributes: { ...ReactNativeViewAttributes.UIView, isHighlighted: true, }, uiViewClassName: 'RCTVirtualText', })); const Text = ( props: TextProps, forwardedRef: ?React.Ref<'RCTText' | 'RCTVirtualText'>, ) => { return ; }; // $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet. const TextToExport = React.forwardRef(Text); // TODO: Deprecate this. TextToExport.propTypes = TextPropTypes; module.exports = (TextToExport: Class>);