Skip to content

Commit

Permalink
Fix issue necolas#1011 - adds touchable event handlers to text
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael S. Kazmier committed Mar 4, 2019
1 parent 000b92e commit 06557c6
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 16 deletions.
1 change: 1 addition & 0 deletions packages/react-native-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"hyphenate-style-name": "^1.0.2",
"inline-style-prefixer": "^5.0.3",
"normalize-css-color": "^1.0.2",
"nullthrows": "^1.1.0",
"prop-types": "^15.6.0",
"react-timer-mixin": "^0.13.4"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ exports[`components/Text prop "onPress" 1`] = `
className="rn-borderTopWidth-13yce4e rn-borderRightWidth-fnigne rn-borderBottomWidth-ndvcnb rn-borderLeftWidth-gxnn5r rn-boxSizing-deolkf rn-color-homxoj rn-cursor-1loqt21 rn-display-1471scf rn-fontFamily-14xgk7a rn-fontSize-1b43r93 rn-fontStyle-o11vmf rn-fontVariant-ebii48 rn-fontWeight-gul640 rn-lineHeight-t9a87b rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw rn-paddingTop-wk8lta rn-paddingRight-9aemit rn-paddingBottom-1mdbw0j rn-paddingLeft-gy4na3 rn-textDecoration-bauka4 rn-whiteSpace-q42fyq rn-wordWrap-qvutc0"
data-focusable={true}
dir="auto"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
tabIndex="0"
/>
`;
Expand Down
148 changes: 133 additions & 15 deletions packages/react-native-web/src/exports/Text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,27 @@ import applyLayout from '../../modules/applyLayout';
import applyNativeMethods from '../../modules/applyNativeMethods';
import { bool } from 'prop-types';
import { Component } from 'react';
import nullthrows from 'nullthrows';
import createElement from '../createElement';
import StyleSheet from '../StyleSheet';
import TextPropTypes from './TextPropTypes';
import Touchable from '../Touchable';

class Text extends Component<*> {
type ResponseHandlers = {|
onStartShouldSetResponder: () => boolean,
onResponderGrant: (event: *, dispatchID: string) => void,
onResponderMove: (event: *) => void,
onResponderRelease: (event: *) => void,
onResponderTerminate: (event: *) => void,
onResponderTerminationRequest: () => boolean
|};

const isTouchable = (props: *): boolean =>
props.onPress != null || props.onLongPress != null || props.onStartShouldSetResponder != null;

const PRESS_RECT_OFFSET = { top: 20, left: 20, right: 20, bottom: 30 };

class Text extends Component<*, *> {
static displayName = 'Text';

static propTypes = TextPropTypes;
Expand All @@ -29,6 +45,40 @@ class Text extends Component<*> {
isInAParentText: bool
};

static getDerivedStateFromProps(nextProps: *, prevState: *): * | null {
return prevState.responseHandlers == null && isTouchable(nextProps)
? {
responseHandlers: prevState.createResponderHandlers()
}
: null;
}

constructor(props) {
super(props);
if (isTouchable(props)) this._attachTouchHandlers();
}

state = {
...Touchable.Mixin.touchableGetInitialState(),
isHighlighted: false,
createResponderHandlers: this._createResponseHandlers.bind(this),
responseHandlers: null
};

// Flow definitions for touchable events
touchableGetPressRectOffset: ?() => *;
touchableHandleActivePressIn: ?() => void;
touchableHandleActivePressOut: ?() => void;
touchableHandleKeyEvent: ?(event: *) => void;
touchableHandleLongPress: ?(event: *) => void;
touchableHandlePress: ?(event: *) => void;
touchableHandleResponderGrant: ?(event: *, dispatchID: string) => void;
touchableHandleResponderMove: ?(event: *) => void;
touchableHandleResponderRelease: ?(event: *) => void;
touchableHandleResponderTerminate: ?(event: *) => void;
touchableHandleResponderTerminationRequest: ?() => boolean;
touchableHandleStartShouldSetResponder: ?() => boolean;

getChildContext() {
return { isInAParentText: true };
}
Expand All @@ -37,7 +87,6 @@ class Text extends Component<*> {
const {
dir,
numberOfLines,
onPress,
selectable,
style,
/* eslint-disable */
Expand All @@ -48,6 +97,7 @@ class Text extends Component<*> {
minimumFontScale,
onLayout,
onLongPress,
onPress,
pressRetentionOffset,
selectionColor,
suppressHighlighting,
Expand All @@ -59,10 +109,16 @@ class Text extends Component<*> {

const { isInAParentText } = this.context;

if (onPress) {
if (isTouchable(this.props)) {
otherProps.accessible = true;
otherProps.onClick = this._createPressHandler(onPress);
otherProps.onKeyDown = this._createEnterHandler(onPress);
otherProps.onKeyDown = this.touchableHandleKeyEvent;
otherProps.onKeyUp = this.touchableHandleKeyEvent;
otherProps.onResponderGrant = this.touchableHandleResponderGrant;
otherProps.onResponderMove = this.touchableHandleResponderMove;
otherProps.onResponderRelease = this.touchableHandleResponderRelease;
otherProps.onResponderTerminate = this.touchableHandleResponderTerminate;
otherProps.onResponderTerminationRequest = this.touchableHandleResponderTerminationRequest;
otherProps.onStartShouldSetResponder = this.touchableHandleStartShouldSetResponder;
}

// allow browsers to automatically infer the language writing direction
Expand All @@ -73,27 +129,89 @@ class Text extends Component<*> {
style,
selectable === false && styles.notSelectable,
numberOfLines === 1 && styles.singleLineStyle,
onPress && styles.pressable
isTouchable(this.props) && styles.pressable
];

const component = isInAParentText ? 'span' : 'div';

return createElement(component, otherProps);
}

_createEnterHandler(fn) {
return e => {
if (e.keyCode === 13) {
fn && fn(e);
_createResponseHandlers(): ResponseHandlers {
return {
onStartShouldSetResponder: (): boolean => {
return isTouchable(this.props);
},
onResponderGrant: (event: *, dispatchID: string): void => {
nullthrows(this.touchableHandleResponderGrant)(event, dispatchID);
if (this.props.onResponderGrant != null) {
this.props.onResponderGrant.call(this, event, dispatchID);
}
},
onResponderMove: (event: *): void => {
nullthrows(this.touchableHandleResponderMove)(event);
if (this.props.onResponderMove != null) {
this.props.onResponderMove.call(this, event);
}
},
onResponderRelease: (event: *): void => {
nullthrows(this.touchableHandleResponderRelease)(event);
if (this.props.onResponderRelease != null) {
this.props.onResponderRelease.call(this, event);
}
},
onResponderTerminate: (event: *): 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();
}
};
}

_createPressHandler(fn) {
return e => {
e.stopPropagation();
fn && fn(e);
/**
* 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: *): void => {
if (this.props.onPress != null) {
this.props.onPress(event);
}
};
this.touchableHandleLongPress = (event: *): void => {
if (this.props.onLongPress != null) {
this.props.onLongPress(event);
}
};
this.touchableGetPressRectOffset = (): * =>
this.props.pressRetentionOffset == null ? PRESS_RECT_OFFSET : this.props.pressRetentionOffset;
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/react-native-web/src/exports/createElement/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const eventHandlerNames = {
onClickCapture: true,
onContextMenu: true,
onFocus: true,
onMouseDown: true,
onMouseUp: true,
onMouseOut: true,
onResponderRelease: true,
onTouchCancel: true,
onTouchCancelCapture: true,
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7615,6 +7615,11 @@ nth-check@~1.0.1:
dependencies:
boolbase "~1.0.0"

nullthrows@^1.1.0:
version "1.1.1"
resolved "https://nexus-gss.uscis.dhs.gov/nexus/repository/didit-npm-group/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1"
integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==

num2fraction@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
Expand Down

0 comments on commit 06557c6

Please sign in to comment.