Skip to content

Commit 06c05e7

Browse files
yungstersfacebook-github-bot
authored andcommitted
RN: Cleanup Text Implementation
Reviewed By: sahrens, TheSavior Differential Revision: D7901531 fbshipit-source-id: dfaba402c1c26e34e9d2df01f2bbb8c26dfcd17e
1 parent a1f2076 commit 06c05e7

File tree

1 file changed

+166
-142
lines changed

1 file changed

+166
-142
lines changed

Libraries/Text/Text.js

+166-142
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* @flow
88
* @format
99
*/
10+
1011
'use strict';
1112

1213
const React = require('React');
@@ -18,17 +19,31 @@ const Touchable = require('Touchable');
1819
const UIManager = require('UIManager');
1920

2021
const createReactNativeComponentClass = require('createReactNativeComponentClass');
22+
const nullthrows = require('fbjs/lib/nullthrows');
2123
const processColor = require('processColor');
2224

2325
import type {PressEvent} from 'CoreEventTypes';
2426
import type {PressRetentionOffset, TextProps} from 'TextProps';
2527

28+
type ResponseHandlers = $ReadOnly<{|
29+
onStartShouldSetResponder: () => boolean,
30+
onResponderGrant: (event: SyntheticEvent<>, dispatchID: string) => void,
31+
onResponderMove: (event: SyntheticEvent<>) => void,
32+
onResponderRelease: (event: SyntheticEvent<>) => void,
33+
onResponderTerminate: (event: SyntheticEvent<>) => void,
34+
onResponderTerminationRequest: () => boolean,
35+
|}>;
36+
37+
type Props = TextProps;
38+
2639
type State = {|
2740
touchable: {|
2841
touchState: ?string,
2942
responderID: ?number,
3043
|},
3144
isHighlighted: boolean,
45+
createResponderHandlers: () => ResponseHandlers,
46+
responseHandlers: ?ResponseHandlers,
3247
|};
3348

3449
const PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
@@ -55,7 +70,7 @@ const viewConfig = {
5570
*
5671
* See https://facebook.github.io/react-native/docs/text.html
5772
*/
58-
class Text extends ReactNative.NativeComponent<TextProps, State> {
73+
class Text extends ReactNative.NativeComponent<Props, State> {
5974
static propTypes = TextPropTypes;
6075

6176
static defaultProps = {
@@ -64,176 +79,185 @@ class Text extends ReactNative.NativeComponent<TextProps, State> {
6479
ellipsizeMode: 'tail',
6580
};
6681

82+
touchableGetPressRectOffset: ?() => PressRetentionOffset;
83+
touchableHandleActivePressIn: ?() => void;
84+
touchableHandleActivePressOut: ?() => void;
85+
touchableHandleLongPress: ?(event: PressEvent) => void;
86+
touchableHandlePress: ?(event: PressEvent) => void;
87+
touchableHandleResponderGrant: ?(
88+
event: SyntheticEvent<>,
89+
dispatchID: string,
90+
) => void;
91+
touchableHandleResponderMove: ?(event: SyntheticEvent<>) => void;
92+
touchableHandleResponderRelease: ?(event: SyntheticEvent<>) => void;
93+
touchableHandleResponderTerminate: ?(event: SyntheticEvent<>) => void;
94+
touchableHandleResponderTerminationRequest: ?() => boolean;
95+
6796
state = {
6897
...Touchable.Mixin.touchableGetInitialState(),
6998
isHighlighted: false,
99+
createResponderHandlers: this._createResponseHandlers.bind(this),
100+
responseHandlers: null,
70101
};
71102

72-
viewConfig = viewConfig;
103+
static getDerivedStateFromProps(nextProps: Props, prevState: State): ?State {
104+
return prevState.responseHandlers == null && isTouchable(nextProps)
105+
? {
106+
...prevState,
107+
responseHandlers: prevState.createResponderHandlers(),
108+
}
109+
: null;
110+
}
73111

74-
_handlers: ?Object;
112+
static viewConfig = viewConfig;
75113

76-
_hasPressHandler(): boolean {
77-
return !!this.props.onPress || !!this.props.onLongPress;
78-
}
79-
/**
80-
* These are assigned lazily the first time the responder is set to make plain
81-
* text nodes as cheap as possible.
82-
*/
83-
touchableHandleActivePressIn: ?Function;
84-
touchableHandleActivePressOut: ?Function;
85-
touchableHandlePress: ?Function;
86-
touchableHandleLongPress: ?Function;
87-
touchableHandleResponderGrant: ?Function;
88-
touchableHandleResponderMove: ?Function;
89-
touchableHandleResponderRelease: ?Function;
90-
touchableHandleResponderTerminate: ?Function;
91-
touchableHandleResponderTerminationRequest: ?Function;
92-
touchableGetPressRectOffset: ?Function;
93-
94-
render(): React.Element<any> {
95-
let newProps = this.props;
96-
if (this.props.onStartShouldSetResponder || this._hasPressHandler()) {
97-
if (!this._handlers) {
98-
this._handlers = {
99-
onStartShouldSetResponder: (): boolean => {
100-
const shouldSetFromProps =
101-
this.props.onStartShouldSetResponder &&
102-
this.props.onStartShouldSetResponder();
103-
const setResponder = shouldSetFromProps || this._hasPressHandler();
104-
if (setResponder && !this.touchableHandleActivePressIn) {
105-
// Attach and bind all the other handlers only the first time a touch
106-
// actually happens.
107-
for (const key in Touchable.Mixin) {
108-
if (typeof Touchable.Mixin[key] === 'function') {
109-
(this: any)[key] = Touchable.Mixin[key].bind(this);
110-
}
111-
}
112-
this.touchableHandleActivePressIn = () => {
113-
if (
114-
this.props.suppressHighlighting ||
115-
!this._hasPressHandler()
116-
) {
117-
return;
118-
}
119-
this.setState({
120-
isHighlighted: true,
121-
});
122-
};
123-
124-
this.touchableHandleActivePressOut = () => {
125-
if (
126-
this.props.suppressHighlighting ||
127-
!this._hasPressHandler()
128-
) {
129-
return;
130-
}
131-
this.setState({
132-
isHighlighted: false,
133-
});
134-
};
135-
136-
this.touchableHandlePress = (e: PressEvent) => {
137-
this.props.onPress && this.props.onPress(e);
138-
};
139-
140-
this.touchableHandleLongPress = (e: PressEvent) => {
141-
this.props.onLongPress && this.props.onLongPress(e);
142-
};
143-
144-
this.touchableGetPressRectOffset = function(): PressRetentionOffset {
145-
return this.props.pressRetentionOffset || PRESS_RECT_OFFSET;
146-
};
147-
}
148-
return setResponder;
149-
},
150-
onResponderGrant: function(e: SyntheticEvent<>, dispatchID: string) {
151-
// $FlowFixMe TouchableMixin handlers couldn't actually be null
152-
this.touchableHandleResponderGrant(e, dispatchID);
153-
this.props.onResponderGrant &&
154-
this.props.onResponderGrant.apply(this, arguments);
155-
}.bind(this),
156-
onResponderMove: function(e: SyntheticEvent<>) {
157-
// $FlowFixMe TouchableMixin handlers couldn't actually be null
158-
this.touchableHandleResponderMove(e);
159-
this.props.onResponderMove &&
160-
this.props.onResponderMove.apply(this, arguments);
161-
}.bind(this),
162-
onResponderRelease: function(e: SyntheticEvent<>) {
163-
// $FlowFixMe TouchableMixin handlers couldn't actually be null
164-
this.touchableHandleResponderRelease(e);
165-
this.props.onResponderRelease &&
166-
this.props.onResponderRelease.apply(this, arguments);
167-
}.bind(this),
168-
onResponderTerminate: function(e: SyntheticEvent<>) {
169-
// $FlowFixMe TouchableMixin handlers couldn't actually be null
170-
this.touchableHandleResponderTerminate(e);
171-
this.props.onResponderTerminate &&
172-
this.props.onResponderTerminate.apply(this, arguments);
173-
}.bind(this),
174-
onResponderTerminationRequest: function(): boolean {
175-
// Allow touchable or props.onResponderTerminationRequest to deny
176-
// the request
177-
// $FlowFixMe TouchableMixin handlers couldn't actually be null
178-
var allowTermination = this.touchableHandleResponderTerminationRequest();
179-
if (allowTermination && this.props.onResponderTerminationRequest) {
180-
allowTermination = this.props.onResponderTerminationRequest.apply(
181-
this,
182-
arguments,
183-
);
184-
}
185-
return allowTermination;
186-
}.bind(this),
187-
};
188-
}
189-
newProps = {
190-
...this.props,
191-
...this._handlers,
114+
render(): React.Node {
115+
let props = this.props;
116+
if (isTouchable(props)) {
117+
props = {
118+
...props,
119+
...this.state.responseHandlers,
192120
isHighlighted: this.state.isHighlighted,
193121
};
194122
}
195-
if (newProps.selectionColor != null) {
196-
newProps = {
197-
...newProps,
198-
selectionColor: processColor(newProps.selectionColor),
123+
if (props.selectionColor != null) {
124+
props = {
125+
...props,
126+
selectionColor: processColor(props.selectionColor),
199127
};
200128
}
201-
if (Touchable.TOUCH_TARGET_DEBUG && newProps.onPress) {
202-
newProps = {
203-
...newProps,
204-
style: [this.props.style, {color: 'magenta'}],
205-
};
129+
if (__DEV__) {
130+
if (Touchable.TOUCH_TARGET_DEBUG && props.onPress != null) {
131+
props = {
132+
...props,
133+
style: [props.style, {color: 'magenta'}],
134+
};
135+
}
206136
}
207137
return (
208138
<TextAncestor.Consumer>
209139
{hasTextAncestor =>
210140
hasTextAncestor ? (
211-
<RCTVirtualText {...newProps} />
141+
<RCTVirtualText {...props} />
212142
) : (
213143
<TextAncestor.Provider value={true}>
214-
<RCTText {...newProps} />
144+
<RCTText {...props} />
215145
</TextAncestor.Provider>
216146
)
217147
}
218148
</TextAncestor.Consumer>
219149
);
220150
}
151+
152+
_createResponseHandlers(): ResponseHandlers {
153+
return {
154+
onStartShouldSetResponder: (): boolean => {
155+
const {onStartShouldSetResponder} = this.props;
156+
const shouldSetResponder =
157+
(onStartShouldSetResponder == null
158+
? false
159+
: onStartShouldSetResponder()) || isTouchable(this.props);
160+
161+
if (shouldSetResponder) {
162+
this._attachTouchHandlers();
163+
}
164+
return shouldSetResponder;
165+
},
166+
onResponderGrant: (event: SyntheticEvent<>, dispatchID: string): void => {
167+
nullthrows(this.touchableHandleResponderGrant)(event, dispatchID);
168+
if (this.props.onResponderGrant != null) {
169+
this.props.onResponderGrant.apply(this, arguments);
170+
}
171+
},
172+
onResponderMove: (event: SyntheticEvent<>): void => {
173+
nullthrows(this.touchableHandleResponderMove)(event);
174+
if (this.props.onResponderMove != null) {
175+
this.props.onResponderMove.apply(this, arguments);
176+
}
177+
},
178+
onResponderRelease: (event: SyntheticEvent<>): void => {
179+
nullthrows(this.touchableHandleResponderRelease)(event);
180+
if (this.props.onResponderRelease != null) {
181+
this.props.onResponderRelease.apply(this, arguments);
182+
}
183+
},
184+
onResponderTerminate: (event: SyntheticEvent<>): void => {
185+
nullthrows(this.touchableHandleResponderTerminate)(event);
186+
if (this.props.onResponderTerminate != null) {
187+
this.props.onResponderTerminate.apply(this, arguments);
188+
}
189+
},
190+
onResponderTerminationRequest: (): boolean => {
191+
const {onResponderTerminationRequest} = this.props;
192+
if (!nullthrows(this.touchableHandleResponderTerminationRequest)()) {
193+
return false;
194+
}
195+
if (onResponderTerminationRequest == null) {
196+
return true;
197+
}
198+
return onResponderTerminationRequest();
199+
},
200+
};
201+
}
202+
203+
/**
204+
* Lazily attaches Touchable.Mixin handlers.
205+
*/
206+
_attachTouchHandlers(): void {
207+
if (this.touchableGetPressRectOffset != null) {
208+
return;
209+
}
210+
for (const key in Touchable.Mixin) {
211+
if (typeof Touchable.Mixin[key] === 'function') {
212+
(this: any)[key] = Touchable.Mixin[key].bind(this);
213+
}
214+
}
215+
this.touchableHandleActivePressIn = (): void => {
216+
if (!this.props.suppressHighlighting && isTouchable(this.props)) {
217+
this.setState({isHighlighted: true});
218+
}
219+
};
220+
this.touchableHandleActivePressOut = (): void => {
221+
if (!this.props.suppressHighlighting && isTouchable(this.props)) {
222+
this.setState({isHighlighted: false});
223+
}
224+
};
225+
this.touchableHandlePress = (event: PressEvent): void => {
226+
if (this.props.onPress != null) {
227+
this.props.onPress(event);
228+
}
229+
};
230+
this.touchableHandleLongPress = (event: PressEvent): void => {
231+
if (this.props.onLongPress != null) {
232+
this.props.onLongPress(event);
233+
}
234+
};
235+
this.touchableGetPressRectOffset = (): PressRetentionOffset =>
236+
this.props.pressRetentionOffset == null
237+
? PRESS_RECT_OFFSET
238+
: this.props.pressRetentionOffset;
239+
}
221240
}
222241

223-
var RCTText = createReactNativeComponentClass(
242+
const isTouchable = (props: Props): boolean =>
243+
props.onPress != null ||
244+
props.onLongPress != null ||
245+
props.onStartShouldSetResponder != null;
246+
247+
const RCTText = createReactNativeComponentClass(
224248
viewConfig.uiViewClassName,
225249
() => viewConfig,
226250
);
227-
var RCTVirtualText = RCTText;
228-
229-
if (UIManager.RCTVirtualText) {
230-
RCTVirtualText = createReactNativeComponentClass('RCTVirtualText', () => ({
231-
validAttributes: {
232-
...ReactNativeViewAttributes.UIView,
233-
isHighlighted: true,
234-
},
235-
uiViewClassName: 'RCTVirtualText',
236-
}));
237-
}
251+
252+
const RCTVirtualText =
253+
UIManager.RCTVirtualText == null
254+
? RCTText
255+
: createReactNativeComponentClass('RCTVirtualText', () => ({
256+
validAttributes: {
257+
...ReactNativeViewAttributes.UIView,
258+
isHighlighted: true,
259+
},
260+
uiViewClassName: 'RCTVirtualText',
261+
}));
238262

239263
module.exports = Text;

0 commit comments

Comments
 (0)