Skip to content

Commit d8e6005

Browse files
committed
Fix #1790. Disabled input still clickable in IE11.
Takes some code from ReactDOMButton in order to solve the same problem within ReactDOMInput, ReactDOMSelect, and ReactDOMTextarea.
1 parent 68a2f89 commit d8e6005

8 files changed

Lines changed: 177 additions & 32 deletions

File tree

src/browser/ui/dom/components/ReactDOMButton.js

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,10 @@ var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
1616
var ReactClass = require('ReactClass');
1717
var ReactElement = require('ReactElement');
1818

19-
var keyMirror = require('keyMirror');
19+
var filterDisabledEvents = require('filterDisabledEvents');
2020

2121
var button = ReactElement.createFactory('button');
2222

23-
var mouseListenerNames = keyMirror({
24-
onClick: true,
25-
onDoubleClick: true,
26-
onMouseDown: true,
27-
onMouseMove: true,
28-
onMouseUp: true,
29-
onClickCapture: true,
30-
onDoubleClickCapture: true,
31-
onMouseDownCapture: true,
32-
onMouseMoveCapture: true,
33-
onMouseUpCapture: true
34-
});
35-
3623
/**
3724
* Implements a <button> native component that does not receive mouse events
3825
* when `disabled` is set.
@@ -44,15 +31,7 @@ var ReactDOMButton = ReactClass.createClass({
4431
mixins: [AutoFocusMixin, ReactBrowserComponentMixin],
4532

4633
render: function() {
47-
var props = {};
48-
49-
// Copy the props; except the mouse listeners if we're disabled
50-
for (var key in this.props) {
51-
if (this.props.hasOwnProperty(key) &&
52-
(!this.props.disabled || !mouseListenerNames[key])) {
53-
props[key] = this.props[key];
54-
}
55-
}
34+
var props = filterDisabledEvents(this.props);
5635

5736
return button(props, this.props.children);
5837
}

src/browser/ui/dom/components/ReactDOMInput.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ var ReactElement = require('ReactElement');
2020
var ReactMount = require('ReactMount');
2121
var ReactUpdates = require('ReactUpdates');
2222

23-
var assign = require('Object.assign');
2423
var findDOMNode = require('findDOMNode');
24+
var filterDisabledEvents = require('filterDisabledEvents');
2525
var invariant = require('invariant');
2626

2727
var input = ReactElement.createFactory('input');
@@ -66,8 +66,7 @@ var ReactDOMInput = ReactClass.createClass({
6666
},
6767

6868
render: function() {
69-
// Clone `this.props` so we don't mutate the input.
70-
var props = assign({}, this.props);
69+
var props = filterDisabledEvents(this.props);
7170

7271
props.defaultChecked = null;
7372
props.defaultValue = null;

src/browser/ui/dom/components/ReactDOMSelect.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ var ReactClass = require('ReactClass');
1818
var ReactElement = require('ReactElement');
1919
var ReactUpdates = require('ReactUpdates');
2020

21-
var assign = require('Object.assign');
2221
var findDOMNode = require('findDOMNode');
22+
var filterDisabledEvents = require('filterDisabledEvents');
2323

2424
var select = ReactElement.createFactory('select');
2525

@@ -122,8 +122,7 @@ var ReactDOMSelect = ReactClass.createClass({
122122
},
123123

124124
render: function() {
125-
// Clone `this.props` so we don't mutate the input.
126-
var props = assign({}, this.props);
125+
var props = filterDisabledEvents(this.props);
127126

128127
props.onChange = this._handleChange;
129128
props.value = null;

src/browser/ui/dom/components/ReactDOMTextarea.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ var ReactClass = require('ReactClass');
1919
var ReactElement = require('ReactElement');
2020
var ReactUpdates = require('ReactUpdates');
2121

22-
var assign = require('Object.assign');
2322
var findDOMNode = require('findDOMNode');
2423
var invariant = require('invariant');
24+
var filterDisabledEvents = require('filterDisabledEvents');
2525

2626
var warning = require('warning');
2727

@@ -95,8 +95,7 @@ var ReactDOMTextarea = ReactClass.createClass({
9595
},
9696

9797
render: function() {
98-
// Clone `this.props` so we don't mutate the input.
99-
var props = assign({}, this.props);
98+
var props = filterDisabledEvents(this.props);
10099

101100
invariant(
102101
props.dangerouslySetInnerHTML == null,

src/browser/ui/dom/components/__tests__/ReactDOMInput-test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@ describe('ReactDOMInput', function() {
2121
var ReactLink;
2222
var ReactTestUtils;
2323

24+
var onClick = mocks.getMockFunction();
25+
26+
function expectClickThru(input) {
27+
onClick.mockClear();
28+
ReactTestUtils.Simulate.click(input.getDOMNode());
29+
expect(onClick.mock.calls.length).toBe(1);
30+
}
31+
32+
function expectNoClickThru(input) {
33+
onClick.mockClear();
34+
ReactTestUtils.Simulate.click(input.getDOMNode());
35+
expect(onClick.mock.calls.length).toBe(0);
36+
}
37+
38+
function mounted(input) {
39+
input = ReactTestUtils.renderIntoDocument(input);
40+
return input;
41+
}
42+
2443
beforeEach(function() {
2544
require('mock-modules').dumpCache();
2645
React = require('React');
@@ -29,6 +48,28 @@ describe('ReactDOMInput', function() {
2948
spyOn(console, 'warn');
3049
});
3150

51+
it('should forward clicks when it starts out not disabled', function() {
52+
expectClickThru(mounted(<input onClick={onClick} />));
53+
});
54+
55+
it('should not forward clicks when it starts out disabled', function() {
56+
expectNoClickThru(
57+
mounted(<input disabled={true} onClick={onClick} />)
58+
);
59+
});
60+
61+
it('should forward clicks when it becomes not disabled', function() {
62+
var input = mounted(<input disabled={true} onClick={onClick} />);
63+
input.setProps({disabled: false});
64+
expectClickThru(input);
65+
});
66+
67+
it('should not forward clicks when it becomes disabled', function() {
68+
var input = mounted(<input onClick={onClick} />);
69+
input.setProps({disabled: true});
70+
expectNoClickThru(input);
71+
});
72+
3273
it('should display `defaultValue` of number 0', function() {
3374
var stub = <input type="text" defaultValue={0} />;
3475
stub = ReactTestUtils.renderIntoDocument(stub);

src/browser/ui/dom/components/__tests__/ReactDOMSelect-test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,53 @@ describe('ReactDOMSelect', function() {
2020
var ReactLink;
2121
var ReactTestUtils;
2222

23+
var onClick = mocks.getMockFunction();
24+
25+
function expectClickThru(select) {
26+
onClick.mockClear();
27+
ReactTestUtils.Simulate.click(select.getDOMNode());
28+
expect(onClick.mock.calls.length).toBe(1);
29+
}
30+
31+
function expectNoClickThru(select) {
32+
onClick.mockClear();
33+
ReactTestUtils.Simulate.click(select.getDOMNode());
34+
expect(onClick.mock.calls.length).toBe(0);
35+
}
36+
37+
function mounted(select) {
38+
select = ReactTestUtils.renderIntoDocument(select);
39+
return select;
40+
}
41+
2342
beforeEach(function() {
2443
React = require('React');
2544
ReactLink = require('ReactLink');
2645
ReactTestUtils = require('ReactTestUtils');
2746
});
2847

48+
it('should forward clicks when it starts out not disabled', function() {
49+
expectClickThru(mounted(<select onClick={onClick} />));
50+
});
51+
52+
it('should not forward clicks when it starts out disabled', function() {
53+
expectNoClickThru(
54+
mounted(<select disabled={true} onClick={onClick} />)
55+
);
56+
});
57+
58+
it('should forward clicks when it becomes not disabled', function() {
59+
var select = mounted(<select disabled={true} onClick={onClick} />);
60+
select.setProps({disabled: false});
61+
expectClickThru(select);
62+
});
63+
64+
it('should not forward clicks when it becomes disabled', function() {
65+
var select = mounted(<select onClick={onClick} />);
66+
select.setProps({disabled: true});
67+
expectNoClickThru(select);
68+
});
69+
2970
it('should allow setting `defaultValue`', function() {
3071
var stub =
3172
<select defaultValue="giraffe">

src/browser/ui/dom/components/__tests__/ReactDOMTextarea-test.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,24 @@ describe('ReactDOMTextarea', function() {
2020
var ReactTestUtils;
2121

2222
var renderTextarea;
23+
var onClick = mocks.getMockFunction();
24+
25+
function expectClickThru(textarea) {
26+
onClick.mockClear();
27+
ReactTestUtils.Simulate.click(textarea.getDOMNode());
28+
expect(onClick.mock.calls.length).toBe(1);
29+
}
30+
31+
function expectNoClickThru(textarea) {
32+
onClick.mockClear();
33+
ReactTestUtils.Simulate.click(textarea.getDOMNode());
34+
expect(onClick.mock.calls.length).toBe(0);
35+
}
36+
37+
function mounted(textarea) {
38+
textarea = ReactTestUtils.renderIntoDocument(textarea);
39+
return textarea;
40+
}
2341

2442
beforeEach(function() {
2543
React = require('React');
@@ -36,6 +54,28 @@ describe('ReactDOMTextarea', function() {
3654
};
3755
});
3856

57+
it('should forward clicks when it starts out not disabled', function() {
58+
expectClickThru(mounted(<textarea onClick={onClick} />));
59+
});
60+
61+
it('should not forward clicks when it starts out disabled', function() {
62+
expectNoClickThru(
63+
mounted(<textarea disabled={true} onClick={onClick} />)
64+
);
65+
});
66+
67+
it('should forward clicks when it becomes not disabled', function() {
68+
var textarea = mounted(<textarea disabled={true} onClick={onClick} />);
69+
textarea.setProps({disabled: false});
70+
expectClickThru(textarea);
71+
});
72+
73+
it('should not forward clicks when it becomes disabled', function() {
74+
var textarea = mounted(<textarea onClick={onClick} />);
75+
textarea.setProps({disabled: true});
76+
expectNoClickThru(textarea);
77+
});
78+
3979
it('should allow setting `defaultValue`', function() {
4080
var stub = <textarea defaultValue="giraffe" />;
4181
stub = renderTextarea(stub);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Copyright 2013-2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule filterDisabledEvents
10+
*/
11+
12+
'use strict';
13+
14+
var assign = require('Object.assign');
15+
var keyMirror = require('keyMirror');
16+
17+
var blacklist = keyMirror({
18+
onClick: true,
19+
onDoubleClick: true,
20+
onMouseDown: true,
21+
onMouseMove: true,
22+
onMouseUp: true,
23+
onClickCapture: true,
24+
onDoubleClickCapture: true,
25+
onMouseDownCapture: true,
26+
onMouseMoveCapture: true,
27+
onMouseUpCapture: true
28+
});
29+
30+
// Copy the props; except the mouse/touch listeners if we're disabled
31+
var filterDisabledEvents = function(props) {
32+
if (!props.disabled) {
33+
return assign({}, props);
34+
}
35+
36+
var accepted = {};
37+
38+
for (var key in props) {
39+
if (props.hasOwnProperty(key) && !blacklist[key]) {
40+
accepted[key] = props[key];
41+
}
42+
}
43+
44+
return accepted;
45+
};
46+
47+
module.exports = filterDisabledEvents;

0 commit comments

Comments
 (0)