@samitouri / QOS-React / commits / fe739c2883

Disabled inputs should not respond to clicks in IE

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.

Nate Hunzaker committed Mar 8, 2016 at 08:13 UTC fe739c28830bbea3588190d7425ba9624621f6a5
7 files changed +166 -124
src/renderers/dom/client/wrappers/DisabledInputUtils.js new
+50
@@ -0,0 +1,50 @@
1 +/**
2 + * Copyright 2013-present, 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 DisabledInputUtils
10 + */
11 +
12 +'use strict';
13 +
14 +var disableableMouseListenerNames = {
15 + onClick: true,
16 + onDoubleClick: true,
17 + onMouseDown: true,
18 + onMouseMove: true,
19 + onMouseUp: true,
20 +
21 + onClickCapture: true,
22 + onDoubleClickCapture: true,
23 + onMouseDownCapture: true,
24 + onMouseMoveCapture: true,
25 + onMouseUpCapture: true,
26 +};
27 +
28 +/**
29 + * Implements a native component that does not receive mouse events
30 + * when `disabled` is set.
31 + */
32 +var DisabledInputUtils = {
33 + getNativeProps: function(inst, props) {
34 + if (!props.disabled) {
35 + return props;
36 + }
37 +
38 + // Copy the props, except the mouse listeners
39 + var nativeProps = {};
40 + for (var key in props) {
41 + if (props.hasOwnProperty(key) && !disableableMouseListenerNames[key]) {
42 + nativeProps[key] = props[key];
43 + }
44 + }
45 +
46 + return nativeProps;
47 + },
48 +};
49 +
50 +module.exports = DisabledInputUtils;
src/renderers/dom/client/wrappers/ReactDOMButton.js
+2 -28
@@ -11,40 +11,14 @@
11
12 'use strict';
13
14 -var mouseListenerNames = {
15 - onClick: true,
16 - onDoubleClick: true,
17 - onMouseDown: true,
18 - onMouseMove: true,
19 - onMouseUp: true,
20 -
21 - onClickCapture: true,
22 - onDoubleClickCapture: true,
23 - onMouseDownCapture: true,
24 - onMouseMoveCapture: true,
25 - onMouseUpCapture: true,
26 -};
14 +var DisabledInputUtils = require('DisabledInputUtils');
15
16 /**
17 * Implements a <button> native component that does not receive mouse events
18 * when `disabled` is set.
19 */
20 var ReactDOMButton = {
33 - getNativeProps: function(inst, props) {
34 - if (!props.disabled) {
35 - return props;
36 - }
37 -
38 - // Copy the props, except the mouse listeners
39 - var nativeProps = {};
40 - for (var key in props) {
41 - if (props.hasOwnProperty(key) && !mouseListenerNames[key]) {
42 - nativeProps[key] = props[key];
43 - }
44 - }
45 -
46 - return nativeProps;
47 - },
21 + getNativeProps: DisabledInputUtils.getNativeProps,
22 };
23
24 module.exports = ReactDOMButton;
src/renderers/dom/client/wrappers/ReactDOMInput.js
+2 -1
@@ -11,6 +11,7 @@
11
12 'use strict';
13
14 +var DisabledInputUtils = require('DisabledInputUtils');
15 var DOMPropertyOperations = require('DOMPropertyOperations');
16 var LinkedValueUtils = require('LinkedValueUtils');
17 var ReactDOMComponentTree = require('ReactDOMComponentTree');
@@ -80,7 +81,7 @@ var ReactDOMInput = {
81 onChange: inst._wrapperState.onChange,
82 });
83
83 - return nativeProps;
84 + return DisabledInputUtils.getNativeProps(inst, nativeProps);
85 },
86
87 mountWrapper: function(inst, props) {
src/renderers/dom/client/wrappers/ReactDOMSelect.js
+2 -1
@@ -11,6 +11,7 @@
11
12 'use strict';
13
14 +var DisabledInputUtils = require('DisabledInputUtils');
15 var LinkedValueUtils = require('LinkedValueUtils');
16 var ReactDOMComponentTree = require('ReactDOMComponentTree');
17 var ReactUpdates = require('ReactUpdates');
@@ -158,7 +159,7 @@ function updateOptions(inst, multiple, propValue) {
159 */
160 var ReactDOMSelect = {
161 getNativeProps: function(inst, props) {
161 - return Object.assign({}, props, {
162 + return Object.assign({}, DisabledInputUtils.getNativeProps(inst, props), {
163 onChange: inst._wrapperState.onChange,
164 value: undefined,
165 });
src/renderers/dom/client/wrappers/ReactDOMTextarea.js
+2 -1
@@ -11,6 +11,7 @@
11
12 'use strict';
13
14 +var DisabledInputUtils = require('DisabledInputUtils');
15 var DOMPropertyOperations = require('DOMPropertyOperations');
16 var LinkedValueUtils = require('LinkedValueUtils');
17 var ReactDOMComponentTree = require('ReactDOMComponentTree');
@@ -67,7 +68,7 @@ var ReactDOMTextarea = {
68
69 // Always set children to the same thing. In IE9, the selection range will
70 // get reset if `textContent` is mutated.
70 - var nativeProps = Object.assign({}, props, {
71 + var nativeProps = Object.assign({}, DisabledInputUtils.getNativeProps(inst, props), {
72 defaultValue: undefined,
73 value: undefined,
74 children: inst._wrapperState.initialValue,
src/renderers/dom/client/wrappers/__tests__/DisabledInputUtil-test.js new
+108
@@ -0,0 +1,108 @@
1 +/**
2 + * Copyright 2013-present, 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 + * @emails react-core
10 + */
11 +
12 +'use strict';
13 +
14 +
15 +describe('DisabledInputUtils', function() {
16 + var React;
17 + var ReactDOM;
18 + var ReactTestUtils;
19 +
20 + var elements = ['button', 'input', 'select', 'textarea'];
21 +
22 + function expectClickThru(element) {
23 + onClick.mockClear();
24 + ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(element));
25 + expect(onClick.mock.calls.length).toBe(1);
26 + }
27 +
28 + function expectNoClickThru(element) {
29 + onClick.mockClear();
30 + ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(element));
31 + expect(onClick.mock.calls.length).toBe(0);
32 + }
33 +
34 + function mounted(element) {
35 + element = ReactTestUtils.renderIntoDocument(element);
36 + return element;
37 + }
38 +
39 + var onClick = jest.genMockFn();
40 +
41 + elements.forEach(function(tagName) {
42 +
43 + describe(tagName, function() {
44 +
45 + beforeEach(function() {
46 + React = require('React');
47 + ReactDOM = require('ReactDOM');
48 + ReactTestUtils = require('ReactTestUtils');
49 + });
50 +
51 + it('should forward clicks when it starts out not disabled', function() {
52 + var element = React.createElement(tagName, {
53 + onClick: onClick,
54 + });
55 +
56 + expectClickThru(mounted(element));
57 + });
58 +
59 + it('should not forward clicks when it starts out disabled', function() {
60 + var element = React.createElement(tagName, {
61 + onClick: onClick,
62 + disabled: true,
63 + });
64 +
65 + expectNoClickThru(mounted(element));
66 + });
67 +
68 + it('should forward clicks when it becomes not disabled', function() {
69 + var container = document.createElement('div');
70 + var element = ReactDOM.render(
71 + React.createElement(tagName, { onClick: onClick, disabled: true }),
72 + container
73 + );
74 + element = ReactDOM.render(
75 + React.createElement(tagName, { onClick: onClick }),
76 + container
77 + );
78 + expectClickThru(element);
79 + });
80 +
81 + it('should not forward clicks when it becomes disabled', function() {
82 + var container = document.createElement('div');
83 + var element = ReactDOM.render(
84 + React.createElement(tagName, { onClick: onClick }),
85 + container
86 + );
87 + element = ReactDOM.render(
88 + React.createElement(tagName, { onClick: onClick, disabled: true }),
89 + container
90 + );
91 + expectNoClickThru(element);
92 + });
93 +
94 + it('should work correctly if the listener is changed', function() {
95 + var container = document.createElement('div');
96 + var element = ReactDOM.render(
97 + React.createElement(tagName, { onClick: onClick, disabled: true }),
98 + container
99 + );
100 + element = ReactDOM.render(
101 + React.createElement(tagName, { onClick: onClick, disabled: false }),
102 + container
103 + );
104 + expectClickThru(element);
105 + });
106 + });
107 + });
108 +});
src/renderers/dom/client/wrappers/__tests__/ReactDOMButton-test.js deleted
-93
@@ -1,93 +0,0 @@
1 -/**
2 - * Copyright 2013-present, 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 - * @emails react-core
10 - */
11 -
12 -'use strict';
13 -
14 -
15 -describe('ReactDOMButton', function() {
16 - var React;
17 - var ReactDOM;
18 - var ReactTestUtils;
19 -
20 - var onClick = jest.genMockFn();
21 -
22 - function expectClickThru(button) {
23 - onClick.mockClear();
24 - ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(button));
25 - expect(onClick.mock.calls.length).toBe(1);
26 - }
27 -
28 - function expectNoClickThru(button) {
29 - onClick.mockClear();
30 - ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(button));
31 - expect(onClick.mock.calls.length).toBe(0);
32 - }
33 -
34 - function mounted(button) {
35 - button = ReactTestUtils.renderIntoDocument(button);
36 - return button;
37 - }
38 -
39 - beforeEach(function() {
40 - React = require('React');
41 - ReactDOM = require('ReactDOM');
42 - ReactTestUtils = require('ReactTestUtils');
43 - });
44 -
45 - it('should forward clicks when it starts out not disabled', function() {
46 - expectClickThru(mounted(<button onClick={onClick} />));
47 - });
48 -
49 - it('should not forward clicks when it starts out disabled', function() {
50 - expectNoClickThru(
51 - mounted(<button disabled={true} onClick={onClick} />)
52 - );
53 - });
54 -
55 - it('should forward clicks when it becomes not disabled', function() {
56 - var container = document.createElement('div');
57 - var btn = ReactDOM.render(
58 - <button disabled={true} onClick={onClick} />,
59 - container
60 - );
61 - btn = ReactDOM.render(
62 - <button onClick={onClick} />,
63 - container
64 - );
65 - expectClickThru(btn);
66 - });
67 -
68 - it('should not forward clicks when it becomes disabled', function() {
69 - var container = document.createElement('div');
70 - var btn = ReactDOM.render(
71 - <button onClick={onClick} />,
72 - container
73 - );
74 - btn = ReactDOM.render(
75 - <button disabled={true} onClick={onClick} />,
76 - container
77 - );
78 - expectNoClickThru(btn);
79 - });
80 -
81 - it('should work correctly if the listener is changed', function() {
82 - var container = document.createElement('div');
83 - var btn = ReactDOM.render(
84 - <button disabled={true} onClick={function() {}} />,
85 - container
86 - );
87 - btn = ReactDOM.render(
88 - <button disabled={false} onClick={onClick} />,
89 - container
90 - );
91 - expectClickThru(btn);
92 - });
93 -});