From 027d8808b7c7f7d87d5312b24df511abfcef22f9 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Tue, 22 Nov 2016 14:39:30 -0800 Subject: [PATCH] Stopgap fix for element disabling (#8387) Fix for #8308. This is a bad hack -- EventPluginHub.getListener isn't even DOM-specific -- but this works for now and lets us release 15.4.1. (cherry picked from commit c7129ce1f0bba7d04e9d5fce806aaeed40730b17) --- .../client/eventPlugins/SimpleEventPlugin.js | 17 +----- .../__tests__/SimpleEventPlugin-test.js | 53 ++++++++++++++++--- .../shared/stack/event/EventPluginHub.js | 30 +++++++++++ .../__tests__/ResponderEventPlugin-test.js | 2 +- 4 files changed, 77 insertions(+), 25 deletions(-) diff --git a/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js b/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js index 5b30244cc648d..4479b447626ed 100644 --- a/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js +++ b/src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js @@ -156,18 +156,6 @@ function isInteractive(tag) { ); } -function shouldPreventMouseEvent(inst) { - if (inst) { - var disabled = inst._currentElement && inst._currentElement.props.disabled; - - if (disabled) { - return isInteractive(inst._tag); - } - } - - return false; -} - var SimpleEventPlugin: PluginModule = { eventTypes: eventTypes, @@ -243,10 +231,7 @@ var SimpleEventPlugin: PluginModule = { case 'topMouseDown': case 'topMouseMove': case 'topMouseUp': - // Disabled elements should not respond to mouse events - if (shouldPreventMouseEvent(targetInst)) { - return null; - } + // TODO: Disabled elements should not respond to mouse events /* falls through */ case 'topMouseOut': case 'topMouseOver': diff --git a/src/renderers/dom/client/eventPlugins/__tests__/SimpleEventPlugin-test.js b/src/renderers/dom/client/eventPlugins/__tests__/SimpleEventPlugin-test.js index d8f30633d10bb..94fb4e6e2f8ce 100644 --- a/src/renderers/dom/client/eventPlugins/__tests__/SimpleEventPlugin-test.js +++ b/src/renderers/dom/client/eventPlugins/__tests__/SimpleEventPlugin-test.js @@ -17,16 +17,14 @@ describe('SimpleEventPlugin', function() { var ReactDOM; var ReactTestUtils; - var onClick = jest.fn(); + var onClick; function expectClickThru(element) { - onClick.mockClear(); ReactTestUtils.SimulateNative.click(ReactDOM.findDOMNode(element)); expect(onClick.mock.calls.length).toBe(1); } function expectNoClickThru(element) { - onClick.mockClear(); ReactTestUtils.SimulateNative.click(ReactDOM.findDOMNode(element)); expect(onClick.mock.calls.length).toBe(0); } @@ -40,6 +38,8 @@ describe('SimpleEventPlugin', function() { React = require('React'); ReactDOM = require('ReactDOM'); ReactTestUtils = require('ReactTestUtils'); + + onClick = jest.fn(); }); it('A non-interactive tags click when disabled', function() { @@ -53,7 +53,48 @@ describe('SimpleEventPlugin', function() { ); var child = ReactDOM.findDOMNode(element).firstChild; - onClick.mockClear(); + ReactTestUtils.SimulateNative.click(child); + expect(onClick.mock.calls.length).toBe(1); + }); + + it('does not register a click when clicking a child of a disabled element', function() { + var element = ReactTestUtils.renderIntoDocument( + + ); + var child = ReactDOM.findDOMNode(element).querySelector('span'); + + ReactTestUtils.SimulateNative.click(child); + expect(onClick.mock.calls.length).toBe(0); + }); + + it('triggers click events for children of disabled elements', function() { + var element = ReactTestUtils.renderIntoDocument( + + ); + var child = ReactDOM.findDOMNode(element).querySelector('span'); + + ReactTestUtils.SimulateNative.click(child); + expect(onClick.mock.calls.length).toBe(1); + }); + + it('triggers parent captured click events when target is a child of a disabled elements', function() { + var element = ReactTestUtils.renderIntoDocument( +
+ +
+ ); + var child = ReactDOM.findDOMNode(element).querySelector('span'); + + ReactTestUtils.SimulateNative.click(child); + expect(onClick.mock.calls.length).toBe(1); + }); + + it('triggers captured click events for children of disabled elements', function() { + var element = ReactTestUtils.renderIntoDocument( + + ); + var child = ReactDOM.findDOMNode(element).querySelector('span'); + ReactTestUtils.SimulateNative.click(child); expect(onClick.mock.calls.length).toBe(1); }); @@ -124,10 +165,6 @@ describe('SimpleEventPlugin', function() { describe('iOS bubbling click fix', function() { // See http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html - beforeEach(function() { - onClick.mockClear(); - }); - it('does not add a local click to interactive elements', function() { var container = document.createElement('div'); diff --git a/src/renderers/shared/stack/event/EventPluginHub.js b/src/renderers/shared/stack/event/EventPluginHub.js index ecff02ebf3879..a7267654354cd 100644 --- a/src/renderers/shared/stack/event/EventPluginHub.js +++ b/src/renderers/shared/stack/event/EventPluginHub.js @@ -59,6 +59,31 @@ var getDictionaryKey = function(inst) { return '.' + inst._rootNodeID; }; +function isInteractive(tag) { + return ( + tag === 'button' || tag === 'input' || + tag === 'select' || tag === 'textarea' + ); +} + +function shouldPreventMouseEvent(name, type, props) { + switch (name) { + case 'onClick': + case 'onClickCapture': + case 'onDoubleClick': + case 'onDoubleClickCapture': + case 'onMouseDown': + case 'onMouseDownCapture': + case 'onMouseMove': + case 'onMouseMoveCapture': + case 'onMouseUp': + case 'onMouseUpCapture': + return !!(props.disabled && isInteractive(type)); + default: + return false; + } +} + /** * This is a unified interface for event plugins to be installed and configured. * @@ -133,7 +158,12 @@ var EventPluginHub = { * @return {?function} The stored callback. */ getListener: function(inst, registrationName) { + // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not + // live here; needs to be moved to a better place soon var bankForRegistrationName = listenerBank[registrationName]; + if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, inst._currentElement.props)) { + return null; + } var key = getDictionaryKey(inst); return bankForRegistrationName && bankForRegistrationName[key]; }, diff --git a/src/renderers/shared/stack/event/eventPlugins/__tests__/ResponderEventPlugin-test.js b/src/renderers/shared/stack/event/eventPlugins/__tests__/ResponderEventPlugin-test.js index 584863a10f001..174cf9bf00391 100644 --- a/src/renderers/shared/stack/event/eventPlugins/__tests__/ResponderEventPlugin-test.js +++ b/src/renderers/shared/stack/event/eventPlugins/__tests__/ResponderEventPlugin-test.js @@ -311,7 +311,7 @@ var CHILD_ID2 = '.0.0.1'; var idToInstance = {}; [GRANDPARENT_ID, PARENT_ID, CHILD_ID, CHILD_ID2].forEach(function(id) { - idToInstance[id] = {_rootNodeID: id}; + idToInstance[id] = {_rootNodeID: id, _currentElement: {}}; }); var three = {