[change] prevent default click behavior when using responder system

Certain HTML elements have a default behaviour (e.g., link navigation)
that can only be prevented in the `click` event handler. The responder
event system doesn't make use of `click` and no callbacks have access to
the `click` event. To prevent unwanted default behaviour, and emulate
the behavior in React Native, the `click` callback will automatically
call `preventDefault()` when the responder system is being used.

The result is that components like `Touchable*` that are overloaded as
web links need to explicitly trigger the link navigation, e.g.,

```
<TouchableOpacity
  accessibilityTraits="link"
  href={href}
  onPress={(e) => {
    Linking.openUrl(href);
  }}
/>
```

Fix #970
This commit is contained in:
Nicolas Gallagher
2018-05-24 12:30:34 -07:00
parent 2afa5a3cf7
commit c336995952
3 changed files with 24 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`modules/createElement it normalizes event.nativeEvent 1`] = `
exports[`modules/createElement normalizes event.nativeEvent 1`] = `
Object {
"_normalized": true,
"bubbles": undefined,
@@ -23,6 +23,6 @@ Object {
}
`;
exports[`modules/createElement it renders different DOM elements 1`] = `<span />`;
exports[`modules/createElement renders different DOM elements 1`] = `<span />`;
exports[`modules/createElement it renders different DOM elements 2`] = `<main />`;
exports[`modules/createElement renders different DOM elements 2`] = `<main />`;

View File

@@ -5,14 +5,14 @@ import React from 'react';
import { render, shallow } from 'enzyme';
describe('modules/createElement', () => {
test('it renders different DOM elements', () => {
test('renders different DOM elements', () => {
let component = render(createElement('span'));
expect(component).toMatchSnapshot();
component = render(createElement('main'));
expect(component).toMatchSnapshot();
});
test('it normalizes event.nativeEvent', done => {
test('normalizes event.nativeEvent', done => {
const onClick = e => {
e.nativeEvent.timestamp = 1496876171255;
expect(e.nativeEvent).toMatchSnapshot();

View File

@@ -20,6 +20,9 @@ EventPluginHub.injection.injectEventPluginsByName({
ResponderEventPlugin
});
const isModifiedEvent = event =>
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
/**
* Ensure event handlers receive an event of the expected shape. The 'button'
* role for accessibility reasons and functional equivalence to the native
@@ -43,7 +46,9 @@ const eventHandlerNames = {
onTouchStartCapture: true
};
const adjustProps = domProps => {
const isButtonRole = domProps.role === 'button';
const { onClick, onResponderRelease, role } = domProps;
const isButtonRole = role === 'button';
const isDisabled = AccessibilityUtil.isDisabled(domProps);
Object.keys(domProps).forEach(propName => {
@@ -62,9 +67,20 @@ const adjustProps = domProps => {
}
});
// Button role should trigger 'onClick' if SPACE or ENTER keys are pressed
// Cancel click events if the responder system is being used. Click events
// are not an expected part of the React Native API, and browsers dispatch
// click events that cannot otherwise be cancelled from preceding mouse
// events in the responder system.
if (onResponderRelease) {
domProps.onClick = function(e) {
if (!e.isDefaultPrevented() && !isModifiedEvent(e.nativeEvent) && !domProps.target) {
e.preventDefault();
}
};
}
// Button role should trigger 'onClick' if SPACE or ENTER keys are pressed.
if (isButtonRole && !isDisabled) {
const { onClick } = domProps;
domProps.onKeyPress = function(e) {
if (!e.isDefaultPrevented() && (e.which === 13 || e.which === 32)) {
e.preventDefault();