Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #1790. Disabled input still clickable in IE11. #1820

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 2 additions & 23 deletions src/browser/ui/dom/components/ReactDOMButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,11 @@ var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactDescriptor = require('ReactDescriptor');
var ReactDOM = require('ReactDOM');

var keyMirror = require('keyMirror');
var filterDisabledEvents = require('filterDisabledEvents');

// Store a reference to the <button> `ReactDOMComponent`. TODO: use string
var button = ReactDescriptor.createFactory(ReactDOM.button.type);

var mouseListenerNames = keyMirror({
onClick: true,
onDoubleClick: true,
onMouseDown: true,
onMouseMove: true,
onMouseUp: true,
onClickCapture: true,
onDoubleClickCapture: true,
onMouseDownCapture: true,
onMouseMoveCapture: true,
onMouseUpCapture: true
});

/**
* Implements a <button> native component that does not receive mouse events
* when `disabled` is set.
Expand All @@ -52,15 +39,7 @@ var ReactDOMButton = ReactCompositeComponent.createClass({
mixins: [AutoFocusMixin, ReactBrowserComponentMixin],

render: function() {
var props = {};

// Copy the props; except the mouse listeners if we're disabled
for (var key in this.props) {
if (this.props.hasOwnProperty(key) &&
(!this.props.disabled || !mouseListenerNames[key])) {
props[key] = this.props[key];
}
}
var props = filterDisabledEvents(this.props);

return button(props, this.props.children);
}
Expand Down
4 changes: 2 additions & 2 deletions src/browser/ui/dom/components/ReactDOMInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ var ReactDOM = require('ReactDOM');
var ReactMount = require('ReactMount');
var ReactUpdates = require('ReactUpdates');

var filterDisabledEvents = require('filterDisabledEvents');
var invariant = require('invariant');
var merge = require('merge');

// Store a reference to the <input> `ReactDOMComponent`. TODO: use string
var input = ReactDescriptor.createFactory(ReactDOM.input.type);
Expand Down Expand Up @@ -74,7 +74,7 @@ var ReactDOMInput = ReactCompositeComponent.createClass({

render: function() {
// Clone `this.props` so we don't mutate the input.
var props = merge(this.props);
var props = filterDisabledEvents(this.props)

props.defaultChecked = null;
props.defaultValue = null;
Expand Down
4 changes: 2 additions & 2 deletions src/browser/ui/dom/components/ReactDOMSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var ReactDescriptor = require('ReactDescriptor');
var ReactDOM = require('ReactDOM');
var ReactUpdates = require('ReactUpdates');

var merge = require('merge');
var filterDisabledEvents = require('filterDisabledEvents');

// Store a reference to the <select> `ReactDOMComponent`. TODO: use string
var select = ReactDescriptor.createFactory(ReactDOM.select.type);
Expand Down Expand Up @@ -138,7 +138,7 @@ var ReactDOMSelect = ReactCompositeComponent.createClass({

render: function() {
// Clone `this.props` so we don't mutate the input.
var props = merge(this.props);
var props = filterDisabledEvents(this.props);

props.onChange = this._handleChange;
props.value = null;
Expand Down
5 changes: 2 additions & 3 deletions src/browser/ui/dom/components/ReactDOMTextarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ var ReactDOM = require('ReactDOM');
var ReactUpdates = require('ReactUpdates');

var invariant = require('invariant');
var merge = require('merge');

var warning = require('warning');

// Store a reference to the <textarea> `ReactDOMComponent`. TODO: use string
Expand All @@ -41,6 +39,7 @@ function forceUpdateIfMounted() {
this.forceUpdate();
}
}
var filterDisabledEvents = require('filterDisabledEvents');

/**
* Implements a <textarea> native component that allows setting `value`, and
Expand Down Expand Up @@ -103,7 +102,7 @@ var ReactDOMTextarea = ReactCompositeComponent.createClass({

render: function() {
// Clone `this.props` so we don't mutate the input.
var props = merge(this.props);
var props = filterDisabledEvents(this.props);

invariant(
props.dangerouslySetInnerHTML == null,
Expand Down
41 changes: 41 additions & 0 deletions src/browser/ui/dom/components/__tests__/ReactDOMInput-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,53 @@ describe('ReactDOMInput', function() {
var ReactLink;
var ReactTestUtils;

var onClick = mocks.getMockFunction();

function expectClickThru(input) {
onClick.mockClear();
ReactTestUtils.Simulate.click(input.getDOMNode());
expect(onClick.mock.calls.length).toBe(1);
}

function expectNoClickThru(input) {
onClick.mockClear();
ReactTestUtils.Simulate.click(input.getDOMNode());
expect(onClick.mock.calls.length).toBe(0);
}

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

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

it('should forward clicks when it starts out not disabled', function() {
expectClickThru(mounted(<input onClick={onClick} />));
});

it('should not forward clicks when it starts out disabled', function() {
expectNoClickThru(
mounted(<input disabled={true} onClick={onClick} />)
);
});

it('should forward clicks when it becomes not disabled', function() {
var input = mounted(<input disabled={true} onClick={onClick} />);
input.setProps({disabled: false});
expectClickThru(input);
});

it('should not forward clicks when it becomes disabled', function() {
var input = mounted(<input onClick={onClick} />);
input.setProps({disabled: true});
expectNoClickThru(input);
});

it('should display `defaultValue` of number 0', function() {
var stub = <input type="text" defaultValue={0} />;
stub = ReactTestUtils.renderIntoDocument(stub);
Expand Down
41 changes: 41 additions & 0 deletions src/browser/ui/dom/components/__tests__/ReactDOMSelect-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,53 @@ describe('ReactDOMSelect', function() {
var ReactLink;
var ReactTestUtils;

var onClick = mocks.getMockFunction();

function expectClickThru(select) {
onClick.mockClear();
ReactTestUtils.Simulate.click(select.getDOMNode());
expect(onClick.mock.calls.length).toBe(1);
}

function expectNoClickThru(select) {
onClick.mockClear();
ReactTestUtils.Simulate.click(select.getDOMNode());
expect(onClick.mock.calls.length).toBe(0);
}

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

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

it('should forward clicks when it starts out not disabled', function() {
expectClickThru(mounted(<select onClick={onClick} />));
});

it('should not forward clicks when it starts out disabled', function() {
expectNoClickThru(
mounted(<select disabled={true} onClick={onClick} />)
);
});

it('should forward clicks when it becomes not disabled', function() {
var select = mounted(<select disabled={true} onClick={onClick} />);
select.setProps({disabled: false});
expectClickThru(select);
});

it('should not forward clicks when it becomes disabled', function() {
var select = mounted(<select onClick={onClick} />);
select.setProps({disabled: true});
expectNoClickThru(select);
});

it('should allow setting `defaultValue`', function() {
var stub =
<select defaultValue="giraffe">
Expand Down
40 changes: 40 additions & 0 deletions src/browser/ui/dom/components/__tests__/ReactDOMTextarea-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ describe('ReactDOMTextarea', function() {
var ReactTestUtils;

var renderTextarea;
var onClick = mocks.getMockFunction();

function expectClickThru(textarea) {
onClick.mockClear();
ReactTestUtils.Simulate.click(textarea.getDOMNode());
expect(onClick.mock.calls.length).toBe(1);
}

function expectNoClickThru(textarea) {
onClick.mockClear();
ReactTestUtils.Simulate.click(textarea.getDOMNode());
expect(onClick.mock.calls.length).toBe(0);
}

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

beforeEach(function() {
React = require('React');
Expand All @@ -43,6 +61,28 @@ describe('ReactDOMTextarea', function() {
};
});

it('should forward clicks when it starts out not disabled', function() {
expectClickThru(mounted(<textarea onClick={onClick} />));
});

it('should not forward clicks when it starts out disabled', function() {
expectNoClickThru(
mounted(<textarea disabled={true} onClick={onClick} />)
);
});

it('should forward clicks when it becomes not disabled', function() {
var textarea = mounted(<textarea disabled={true} onClick={onClick} />);
textarea.setProps({disabled: false});
expectClickThru(textarea);
});

it('should not forward clicks when it becomes disabled', function() {
var textarea = mounted(<textarea onClick={onClick} />);
textarea.setProps({disabled: true});
expectNoClickThru(textarea);
});

it('should allow setting `defaultValue`', function() {
var stub = <textarea defaultValue="giraffe" />;
stub = renderTextarea(stub);
Expand Down
52 changes: 52 additions & 0 deletions src/browser/ui/dom/filterDisabledEvents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright 2013-2014 Facebook, Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2014 should become 2015 before this is merged.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the old license even :)

*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule filterDisabledEvents
*/

"use strict";

var merge = require('merge');
var keyMirror = require('keyMirror');

var blacklist = keyMirror({
onClick: true,
onDoubleClick: true,
onMouseDown: true,
onMouseMove: true,
onMouseUp: true,
onClickCapture: true,
onDoubleClickCapture: true,
onMouseDownCapture: true,
onMouseMoveCapture: true,
onMouseUpCapture: true
});

// Copy the props; except the mouse/touch listeners if we're disabled
var filterDisabledEvents = function(props) {
if (!props.disabled) return merge(props);

var accepted = {};

for (var key in props) {
if (props.hasOwnProperty(key) && !blacklist[key]) {
accepted[key] = props[key];
}
}

return accepted;
}

module.exports = filterDisabledEvents