UNPKG

react-native-windows

Version:
261 lines (238 loc) • 8.33 kB
/** * 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 TextInjection from './TextInjection'; import {NativeText, NativeVirtualText} from './TextNativeComponent'; const DeprecatedTextPropTypes = require('../DeprecatedPropTypes/DeprecatedTextPropTypes'); const React = require('react'); const TextAncestor = require('./TextAncestor'); const Touchable = require('../Components/Touchable/Touchable'); const nullthrows = require('nullthrows'); const processColor = require('../StyleSheet/processColor'); import type {PressEvent} from '../Types/CoreEventTypes'; import type {PressRetentionOffset, TextProps} from './TextProps'; type ResponseHandlers = $ReadOnly<{| onStartShouldSetResponder: () => boolean, onResponderGrant: (event: PressEvent) => void, onResponderMove: (event: PressEvent) => void, onResponderRelease: (event: PressEvent) => void, onResponderTerminate: (event: PressEvent) => void, onResponderTerminationRequest: () => boolean, |}>; type Props = $ReadOnly<{| ...TextProps, forwardedRef: ?React.Ref<typeof NativeText | typeof NativeVirtualText>, |}>; 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}; /** * A React component for displaying text. * * See https://reactnative.dev/docs/text.html */ class TouchableText extends React.Component<Props, State> { static defaultProps = { accessible: true, allowFontScaling: true, ellipsizeMode: 'tail', }; touchableGetPressRectOffset: ?() => PressRetentionOffset; touchableHandleActivePressIn: ?() => void; touchableHandleActivePressOut: ?() => void; touchableHandleLongPress: ?(event: PressEvent) => void; touchableHandlePress: ?(event: PressEvent) => void; touchableHandleResponderGrant: ?(event: PressEvent) => void; touchableHandleResponderMove: ?(event: PressEvent) => void; touchableHandleResponderRelease: ?(event: PressEvent) => void; touchableHandleResponderTerminate: ?(event: PressEvent) => void; touchableHandleResponderTerminationRequest: ?() => boolean; state = { ...Touchable.Mixin.touchableGetInitialState(), isHighlighted: false, createResponderHandlers: this._createResponseHandlers.bind(this), responseHandlers: null, }; static getDerivedStateFromProps( nextProps: Props, prevState: State, ): $Shape<State> | null { return prevState.responseHandlers == null && isTouchable(nextProps) ? { responseHandlers: prevState.createResponderHandlers(), } : null; } render(): React.Node { let {forwardedRef, selectionColor, ...props} = this.props; if (isTouchable(this.props)) { props = { ...props, ...this.state.responseHandlers, isHighlighted: this.state.isHighlighted, }; } if (selectionColor != null) { props = { ...props, selectionColor: processColor(selectionColor), }; } if (__DEV__) { if (Touchable.TOUCH_TARGET_DEBUG && props.onPress != null) { props = { ...props, style: [props.style, {color: 'magenta'}], }; } } return ( <TextAncestor.Consumer> {hasTextAncestor => hasTextAncestor ? ( // $FlowFixMe[prop-missing] For the `onClick` workaround. <NativeVirtualText {...props} // This is used on Android to call a nested Text component's press handler from the context menu. // TODO T75145059 Clean this up once Text is migrated off of Touchable onClick={props.onPress} ref={forwardedRef} /> ) : ( <TextAncestor.Provider value={true}> <NativeText {...props} ref={forwardedRef} /> </TextAncestor.Provider> ) } </TextAncestor.Consumer> ); } _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: PressEvent): void => { nullthrows(this.touchableHandleResponderGrant)(event); if (this.props.onResponderGrant != null) { this.props.onResponderGrant.call(this, event); } }, onResponderMove: (event: PressEvent): void => { nullthrows(this.touchableHandleResponderMove)(event); if (this.props.onResponderMove != null) { this.props.onResponderMove.call(this, event); } }, onResponderRelease: (event: PressEvent): void => { nullthrows(this.touchableHandleResponderRelease)(event); if (this.props.onResponderRelease != null) { this.props.onResponderRelease.call(this, event); } }, onResponderTerminate: (event: PressEvent): 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 Text: React.AbstractComponent< TextProps, React.ElementRef<typeof NativeText | typeof NativeVirtualText>, > = React.forwardRef( ( props: TextProps, forwardedRef: ?React.Ref<typeof NativeText | typeof NativeVirtualText>, ) => { return <TouchableText {...props} forwardedRef={forwardedRef} />; }, ); Text.displayName = 'Text'; // TODO: Deprecate this. /* $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. */ Text.propTypes = DeprecatedTextPropTypes; const TextToExport: typeof Text & $ReadOnly<{| propTypes: typeof DeprecatedTextPropTypes, |}> = // $FlowFixMe[incompatible-type] - No good way to type a React.AbstractComponent with statics. TextInjection.unstable_Text == null ? Text : TextInjection.unstable_Text; module.exports = TextToExport;