mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
b1251d067a
Summary: We want the ability to use Linkify on android text elements. This only adds this property to Text and not TextInput since there are some functional differences with how the types could be used between iOS and android - iOS allows one or many types while Linkify restricted us to providing only one option (using the masks). Performance is affected ONLY FOR TEXT ELEMENTS USING THIS FEATURE since Linkify is searching for patterns. Pull Request resolved: https://github.com/facebook/react-native/pull/19216 Differential Revision: D14621883 Pulled By: cpojer fbshipit-source-id: cb692021d314140b9a92b29e23384afd7fd1b09e
293 lines
8.7 KiB
JavaScript
293 lines
8.7 KiB
JavaScript
/**
|
|
* 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';
|
|
|
|
const DeprecatedTextPropTypes = require('DeprecatedTextPropTypes');
|
|
const React = require('React');
|
|
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
|
const TextAncestor = require('TextAncestor');
|
|
const Touchable = require('Touchable');
|
|
const UIManager = require('UIManager');
|
|
|
|
const createReactNativeComponentClass = require('createReactNativeComponentClass');
|
|
const nullthrows = require('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: PressEvent, dispatchID: string) => void,
|
|
onResponderMove: (event: PressEvent) => void,
|
|
onResponderRelease: (event: PressEvent) => void,
|
|
onResponderTerminate: (event: PressEvent) => 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,
|
|
maxFontSizeMultiplier: true,
|
|
disabled: true,
|
|
selectable: true,
|
|
selectionColor: true,
|
|
adjustsFontSizeToFit: true,
|
|
minimumFontScale: true,
|
|
textBreakStrategy: true,
|
|
onTextLayout: true,
|
|
dataDetectorType: 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<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,
|
|
dispatchID: string,
|
|
) => 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;
|
|
}
|
|
|
|
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 (
|
|
<TextAncestor.Consumer>
|
|
{hasTextAncestor =>
|
|
hasTextAncestor ? (
|
|
<RCTVirtualText {...props} ref={props.forwardedRef} />
|
|
) : (
|
|
<TextAncestor.Provider value={true}>
|
|
<RCTText {...props} ref={props.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, dispatchID: string): void => {
|
|
nullthrows(this.touchableHandleResponderGrant)(event, dispatchID);
|
|
if (this.props.onResponderGrant != null) {
|
|
this.props.onResponderGrant.call(this, event, dispatchID);
|
|
}
|
|
},
|
|
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 RCTText = createReactNativeComponentClass(
|
|
viewConfig.uiViewClassName,
|
|
() => viewConfig,
|
|
);
|
|
|
|
const RCTVirtualText =
|
|
UIManager.getViewManagerConfig('RCTVirtualText') == null
|
|
? RCTText
|
|
: createReactNativeComponentClass('RCTVirtualText', () => ({
|
|
validAttributes: {
|
|
...ReactNativeViewAttributes.UIView,
|
|
isHighlighted: true,
|
|
maxFontSizeMultiplier: true,
|
|
},
|
|
uiViewClassName: 'RCTVirtualText',
|
|
}));
|
|
|
|
const Text = (
|
|
props: TextProps,
|
|
forwardedRef: ?React.Ref<'RCTText' | 'RCTVirtualText'>,
|
|
) => {
|
|
return <TouchableText {...props} forwardedRef={forwardedRef} />;
|
|
};
|
|
const TextToExport = React.forwardRef(Text);
|
|
TextToExport.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. */
|
|
TextToExport.propTypes = DeprecatedTextPropTypes;
|
|
|
|
module.exports = ((TextToExport: $FlowFixMe): Class<
|
|
NativeComponent<TextProps>,
|
|
>);
|