Skip to content

Commit

Permalink
Merge pull request react-bootstrap#95 from glenjamin/mousedown
Browse files Browse the repository at this point in the history
Make <RootCloseWrapper /> hide onmousedown instead of onclick
  • Loading branch information
jquense committed Jul 8, 2016
2 parents b539481 + 07173cb commit 0bbe0cb
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 63 deletions.
30 changes: 20 additions & 10 deletions src/RootCloseWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default class RootCloseWrapper extends React.Component {
constructor(props) {
super(props);

this.handleDocumentClick = this.handleDocumentClick.bind(this);
this.handleDocumentMouse = this.handleDocumentMouse.bind(this);
this.handleDocumentKeyUp = this.handleDocumentKeyUp.bind(this);

let { id, suppressRootClose } = getSuppressRootClose();
Expand All @@ -48,14 +48,14 @@ export default class RootCloseWrapper extends React.Component {
bindRootCloseHandlers() {
const doc = ownerDocument(this);

this._onDocumentClickListener =
addEventListener(doc, 'click', this.handleDocumentClick);
this._onDocumentMouseListener =
addEventListener(doc, this.props.event, this.handleDocumentMouse);

this._onDocumentKeyupListener =
addEventListener(doc, 'keyup', this.handleDocumentKeyUp);
}

handleDocumentClick(e) {
handleDocumentMouse(e) {
// This is now the native event.
if (e[this._suppressRootId]) {
return;
Expand All @@ -76,8 +76,8 @@ export default class RootCloseWrapper extends React.Component {
}

unbindRootCloseHandlers() {
if (this._onDocumentClickListener) {
this._onDocumentClickListener.remove();
if (this._onDocumentMouseListener) {
this._onDocumentMouseListener.remove();
}

if (this._onDocumentKeyupListener) {
Expand All @@ -90,19 +90,21 @@ export default class RootCloseWrapper extends React.Component {
}

render() {
const {noWrap, children} = this.props;
const {event, noWrap, children} = this.props;
const child = React.Children.only(children);

const handlerName = event == 'click' ? 'onClick' : 'onMouseDown';

if (noWrap) {
return React.cloneElement(child, {
onClick: createChainedFunction(this._suppressRootCloseHandler, child.props.onClick)
[handlerName]: createChainedFunction(this._suppressRootCloseHandler, child.props[handlerName])
});
}

// Wrap the child in a new element, so the child won't have to handle
// potentially combining multiple onClick listeners.
return (
<div onClick={this._suppressRootCloseHandler}>
<div {...{[handlerName]: this._suppressRootCloseHandler}}>
{child}
</div>
);
Expand Down Expand Up @@ -137,5 +139,13 @@ RootCloseWrapper.propTypes = {
* of placing it on a wrapping div. Only use when you can be sure the child
* properly handle the click event.
*/
noWrap: React.PropTypes.bool
noWrap: React.PropTypes.bool,
/**
* Choose which document mouse event to bind to
*/
event: React.PropTypes.oneOf(['click', 'mousedown'])
};

RootCloseWrapper.defaultProps = {
event: 'click'
};
119 changes: 66 additions & 53 deletions test/RootCloseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,76 +17,89 @@ describe('RootCloseWrapper', function () {
document.body.removeChild(mountPoint);
});

it('should close when clicked outside', () => {
let spy = sinon.spy();
render(
<RootCloseWrapper onRootClose={spy}>
<div id='my-div'>hello there</div>
</RootCloseWrapper>
, mountPoint);
describe('using default event', () => {
shouldCloseOn(undefined, 'click');
});

simulant.fire(document.getElementById('my-div'), 'click');
describe('using click event', () => {
shouldCloseOn('click', 'click');
});

expect(spy).to.not.have.been.called;
describe('using mousedown event', () => {
shouldCloseOn('mousedown', 'mousedown');
});

simulant.fire(document.body, 'click');
function shouldCloseOn(eventProp, eventName) {
it('should close when clicked outside', () => {
let spy = sinon.spy();
render(
<RootCloseWrapper onRootClose={spy} event={eventProp}>
<div id='my-div'>hello there</div>
</RootCloseWrapper>
, mountPoint);

expect(spy).to.have.been.calledOnce;
});
simulant.fire(document.getElementById('my-div'), eventName);

it('should not close when right-clicked outside', () => {
let spy = sinon.spy();
render(
<RootCloseWrapper onRootClose={spy}>
<div id='my-div'>hello there</div>
</RootCloseWrapper>
, mountPoint);
expect(spy).to.not.have.been.called;

simulant.fire(document.getElementById('my-div'), 'click', {button: 1});
simulant.fire(document.body, eventName);

expect(spy).to.not.have.been.called;
expect(spy).to.have.been.calledOnce;
});

simulant.fire(document.body, 'click', {button: 1});
it('should not close when right-clicked outside', () => {
let spy = sinon.spy();
render(
<RootCloseWrapper onRootClose={spy} event={eventProp}>
<div id='my-div'>hello there</div>
</RootCloseWrapper>
, mountPoint);

expect(spy).to.not.have.been.called;
});
simulant.fire(document.getElementById('my-div'), eventName, {button: 1});

it('should not close when disabled', () => {
let spy = sinon.spy();
render(
<RootCloseWrapper onRootClose={spy} disabled>
<div id='my-div'>hello there</div>
</RootCloseWrapper>
, mountPoint);
expect(spy).to.not.have.been.called;

simulant.fire(document.getElementById('my-div'), 'click');
simulant.fire(document.body, eventName, {button: 1});

expect(spy).to.not.have.been.called;
expect(spy).to.not.have.been.called;
});

simulant.fire(document.body, 'click');
it('should not close when disabled', () => {
let spy = sinon.spy();
render(
<RootCloseWrapper onRootClose={spy} event={eventProp} disabled>
<div id='my-div'>hello there</div>
</RootCloseWrapper>
, mountPoint);

expect(spy).to.not.have.been.called;
});
simulant.fire(document.getElementById('my-div'), eventName);

it('should close when inside another RootCloseWrapper', () => {
let outerSpy = sinon.spy();
let innerSpy = sinon.spy();
expect(spy).to.not.have.been.called;

render(
<RootCloseWrapper onRootClose={outerSpy}>
<div>
<div id='my-div'>hello there</div>
<RootCloseWrapper onRootClose={innerSpy}>
<div id='my-other-div'>hello there</div>
</RootCloseWrapper>
</div>
</RootCloseWrapper>
, mountPoint);
simulant.fire(document.body, eventName);

simulant.fire(document.getElementById('my-div'), 'click');
expect(spy).to.not.have.been.called;
});

expect(outerSpy).to.have.not.been.called;
expect(innerSpy).to.have.been.calledOnce;
});
it('should close when inside another RootCloseWrapper', () => {
let outerSpy = sinon.spy();
let innerSpy = sinon.spy();

render(
<RootCloseWrapper onRootClose={outerSpy} event={eventProp}>
<div>
<div id='my-div'>hello there</div>
<RootCloseWrapper onRootClose={innerSpy} event={eventProp}>
<div id='my-other-div'>hello there</div>
</RootCloseWrapper>
</div>
</RootCloseWrapper>
, mountPoint);

simulant.fire(document.getElementById('my-div'), eventName);

expect(outerSpy).to.have.not.been.called;
expect(innerSpy).to.have.been.calledOnce;
});
}
});

0 comments on commit 0bbe0cb

Please sign in to comment.