Skip to content

Commit

Permalink
Disabled inputs should not respond to clicks in IE
Browse files Browse the repository at this point in the history
This commit migrates over the disabled property behavior from
ReactDOMButton into a general purpose disabled event filter. It also
applies that behavior to inputs, selects, and textareas.
  • Loading branch information
nhunzaker committed Mar 17, 2016
1 parent 8bff1f5 commit 0e63de5
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 124 deletions.
50 changes: 50 additions & 0 deletions src/renderers/dom/client/wrappers/DisabledInputUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule DisabledInputUtils
*/

'use strict';

var disableableMouseListenerNames = {
onClick: true,
onDoubleClick: true,
onMouseDown: true,
onMouseMove: true,
onMouseUp: true,

onClickCapture: true,
onDoubleClickCapture: true,
onMouseDownCapture: true,
onMouseMoveCapture: true,
onMouseUpCapture: true,
};

/**
* Implements a native component that does not receive mouse events
* when `disabled` is set.
*/
var DisabledInputUtils = {
getNativeProps: function(inst, props) {
if (!props.disabled) {
return props;
}

// Copy the props, except the mouse listeners
var nativeProps = {};
for (var key in props) {
if (props.hasOwnProperty(key) && !disableableMouseListenerNames[key]) {
nativeProps[key] = props[key];
}
}

return nativeProps;
},
};

module.exports = DisabledInputUtils;
30 changes: 2 additions & 28 deletions src/renderers/dom/client/wrappers/ReactDOMButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,14 @@

'use strict';

var mouseListenerNames = {
onClick: true,
onDoubleClick: true,
onMouseDown: true,
onMouseMove: true,
onMouseUp: true,

onClickCapture: true,
onDoubleClickCapture: true,
onMouseDownCapture: true,
onMouseMoveCapture: true,
onMouseUpCapture: true,
};
var DisabledInputUtils = require('DisabledInputUtils');

/**
* Implements a <button> native component that does not receive mouse events
* when `disabled` is set.
*/
var ReactDOMButton = {
getNativeProps: function(inst, props) {
if (!props.disabled) {
return props;
}

// Copy the props, except the mouse listeners
var nativeProps = {};
for (var key in props) {
if (props.hasOwnProperty(key) && !mouseListenerNames[key]) {
nativeProps[key] = props[key];
}
}

return nativeProps;
},
getNativeProps: DisabledInputUtils.getNativeProps,
};

module.exports = ReactDOMButton;
3 changes: 2 additions & 1 deletion src/renderers/dom/client/wrappers/ReactDOMInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

'use strict';

var DisabledInputUtils = require('DisabledInputUtils');
var DOMPropertyOperations = require('DOMPropertyOperations');
var LinkedValueUtils = require('LinkedValueUtils');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
Expand Down Expand Up @@ -81,7 +82,7 @@ var ReactDOMInput = {
onChange: inst._wrapperState.onChange,
});

return nativeProps;
return DisabledInputUtils.getNativeProps(inst, nativeProps);
},

mountWrapper: function(inst, props) {
Expand Down
3 changes: 2 additions & 1 deletion src/renderers/dom/client/wrappers/ReactDOMSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

'use strict';

var DisabledInputUtils = require('DisabledInputUtils');
var LinkedValueUtils = require('LinkedValueUtils');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactUpdates = require('ReactUpdates');
Expand Down Expand Up @@ -159,7 +160,7 @@ function updateOptions(inst, multiple, propValue) {
*/
var ReactDOMSelect = {
getNativeProps: function(inst, props) {
return assign({}, props, {
return assign({}, DisabledInputUtils.getNativeProps(inst, props), {
onChange: inst._wrapperState.onChange,
value: undefined,
});
Expand Down
3 changes: 2 additions & 1 deletion src/renderers/dom/client/wrappers/ReactDOMTextarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

'use strict';

var DisabledInputUtils = require('DisabledInputUtils');
var DOMPropertyOperations = require('DOMPropertyOperations');
var LinkedValueUtils = require('LinkedValueUtils');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
Expand Down Expand Up @@ -68,7 +69,7 @@ var ReactDOMTextarea = {

// Always set children to the same thing. In IE9, the selection range will
// get reset if `textContent` is mutated.
var nativeProps = assign({}, props, {
var nativeProps = assign({}, DisabledInputUtils.getNativeProps(inst, props), {
defaultValue: undefined,
value: undefined,
children: inst._wrapperState.initialValue,
Expand Down
108 changes: 108 additions & 0 deletions src/renderers/dom/client/wrappers/__tests__/DisabledInputUtil-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/

'use strict';


describe('DisabledInputUtils', function() {
var React;
var ReactDOM;
var ReactTestUtils;

var elements = ['button', 'input', 'select', 'textarea'];

function expectClickThru(element) {
onClick.mockClear();
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(element));
expect(onClick.mock.calls.length).toBe(1);
}

function expectNoClickThru(element) {
onClick.mockClear();
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(element));
expect(onClick.mock.calls.length).toBe(0);
}

function mounted(element) {
element = ReactTestUtils.renderIntoDocument(element);
return element;
}

var onClick = jest.genMockFn();

elements.forEach(function(tagName) {

describe(tagName, function() {

beforeEach(function() {
React = require('React');
ReactDOM = require('ReactDOM');
ReactTestUtils = require('ReactTestUtils');
});

it('should forward clicks when it starts out not disabled', function() {
var element = React.createElement(tagName, {
onClick: onClick,
});

expectClickThru(mounted(element));
});

it('should not forward clicks when it starts out disabled', function() {
var element = React.createElement(tagName, {
onClick: onClick,
disabled: true,
});

expectNoClickThru(mounted(element));
});

it('should forward clicks when it becomes not disabled', function() {
var container = document.createElement('div');
var element = ReactDOM.render(
React.createElement(tagName, { onClick: onClick, disabled: true }),
container
);
element = ReactDOM.render(
React.createElement(tagName, { onClick: onClick }),
container
);
expectClickThru(element);
});

it('should not forward clicks when it becomes disabled', function() {
var container = document.createElement('div');
var element = ReactDOM.render(
React.createElement(tagName, { onClick: onClick }),
container
);
element = ReactDOM.render(
React.createElement(tagName, { onClick: onClick, disabled: true }),
container
);
expectNoClickThru(element);
});

it('should work correctly if the listener is changed', function() {
var container = document.createElement('div');
var element = ReactDOM.render(
React.createElement(tagName, { onClick: onClick, disabled: true }),
container
);
element = ReactDOM.render(
React.createElement(tagName, { onClick: onClick, disabled: false }),
container
);
expectClickThru(element);
});
});
});
});
93 changes: 0 additions & 93 deletions src/renderers/dom/client/wrappers/__tests__/ReactDOMButton-test.js

This file was deleted.

0 comments on commit 0e63de5

Please sign in to comment.