From c33699595281416d67faa1d380b7dba812bd8ebe Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Thu, 24 May 2018 12:30:34 -0700 Subject: [PATCH] [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., ``` { Linking.openUrl(href); }} /> ``` Fix #970 --- .../__snapshots__/index-test.js.snap | 6 ++--- .../createElement/__tests__/index-test.js | 4 ++-- .../src/exports/createElement/index.js | 22 ++++++++++++++++--- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/react-native-web/src/exports/createElement/__tests__/__snapshots__/index-test.js.snap b/packages/react-native-web/src/exports/createElement/__tests__/__snapshots__/index-test.js.snap index c86761eb..4b97c76d 100644 --- a/packages/react-native-web/src/exports/createElement/__tests__/__snapshots__/index-test.js.snap +++ b/packages/react-native-web/src/exports/createElement/__tests__/__snapshots__/index-test.js.snap @@ -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`] = ``; +exports[`modules/createElement renders different DOM elements 1`] = ``; -exports[`modules/createElement it renders different DOM elements 2`] = `
`; +exports[`modules/createElement renders different DOM elements 2`] = `
`; diff --git a/packages/react-native-web/src/exports/createElement/__tests__/index-test.js b/packages/react-native-web/src/exports/createElement/__tests__/index-test.js index bf75e2a9..a4d5db0d 100644 --- a/packages/react-native-web/src/exports/createElement/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/createElement/__tests__/index-test.js @@ -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(); diff --git a/packages/react-native-web/src/exports/createElement/index.js b/packages/react-native-web/src/exports/createElement/index.js index 0020bf05..32780318 100644 --- a/packages/react-native-web/src/exports/createElement/index.js +++ b/packages/react-native-web/src/exports/createElement/index.js @@ -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();