Skip to content

Commit

Permalink
Allows nesting root close wrappers
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Oct 7, 2015
1 parent c3613fb commit aedad0b
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 8 deletions.
29 changes: 21 additions & 8 deletions src/RootCloseWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ import ownerDocument from './utils/ownerDocument';
// TODO: Consider using an ES6 symbol here, once we use babel-runtime.
const CLICK_WAS_INSIDE = '__click_was_inside';

function suppressRootClose(event) {
// Tag the native event to prevent the root close logic on document click.
// This seems safer than using event.nativeEvent.stopImmediatePropagation(),
// which is only supported in IE >= 9.
event.nativeEvent[CLICK_WAS_INSIDE] = true;
let counter = 0;

function getSuppressRootClose() {
let id = CLICK_WAS_INSIDE + '_' + counter++;
return {
id,
suppressRootClose(event) {
// Tag the native event to prevent the root close logic on document click.
// This seems safer than using event.nativeEvent.stopImmediatePropagation(),
// which is only supported in IE >= 9.
event.nativeEvent[id] = true;
}
};
}

export default class RootCloseWrapper extends React.Component {
Expand All @@ -19,6 +27,11 @@ export default class RootCloseWrapper extends React.Component {

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

let { id, suppressRootClose } = getSuppressRootClose();

this._suppressRootId = id;
this._suppressRootClosehHandler = suppressRootClose;
}

bindRootCloseHandlers() {
Expand All @@ -33,7 +46,7 @@ export default class RootCloseWrapper extends React.Component {

handleDocumentClick(e) {
// This is now the native event.
if (e[CLICK_WAS_INSIDE]) {
if (e[this._suppressRootId]) {
return;
}

Expand Down Expand Up @@ -66,14 +79,14 @@ export default class RootCloseWrapper extends React.Component {

if (noWrap) {
return React.cloneElement(child, {
onClick: createChainedFunction(suppressRootClose, child.props.onClick)
onClick: createChainedFunction(this._suppressRootClosehHandler, child.props.onClick)
});
}

// Wrap the child in a new element, so the child won't have to handle
// potentially combining multiple onClick listeners.
return (
<div onClick={suppressRootClose}>
<div onClick={this._suppressRootClosehHandler}>
{child}
</div>
);
Expand Down
58 changes: 58 additions & 0 deletions test/RootCloseSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import ReactDOM from 'react-dom';
import RootCloseWrapper from '../src/RootCloseWrapper';
import { render } from './helpers';
import simulant from 'simulant';

describe('RootCloseWrapper', function () {
let mountPoint;

beforeEach(()=>{
mountPoint = document.createElement('div');
document.body.appendChild(mountPoint);
});

afterEach(function () {
ReactDOM.unmountComponentAtNode(mountPoint);
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);

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

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

simulant.fire(document.body, 'click');

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

it('should close when inside another RootCloseWrapper', () => {
let outerSpy = sinon.spy();
let innerSpy = sinon.spy();

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.getElementById('my-div'), 'click');

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

});

0 comments on commit aedad0b

Please sign in to comment.