Skip to content

Commit be3a5b6

Browse files
committed
Merge pull request #20 from react-bootstrap/nested-root-close
Allows nesting root close wrappers
2 parents 1bb2caf + d3853ed commit be3a5b6

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed

src/RootCloseWrapper.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,19 @@ import ownerDocument from './utils/ownerDocument';
88
// TODO: Consider using an ES6 symbol here, once we use babel-runtime.
99
const CLICK_WAS_INSIDE = '__click_was_inside';
1010

11-
function suppressRootClose(event) {
12-
// Tag the native event to prevent the root close logic on document click.
13-
// This seems safer than using event.nativeEvent.stopImmediatePropagation(),
14-
// which is only supported in IE >= 9.
15-
event.nativeEvent[CLICK_WAS_INSIDE] = true;
11+
let counter = 0;
12+
13+
function getSuppressRootClose() {
14+
let id = CLICK_WAS_INSIDE + '_' + counter++;
15+
return {
16+
id,
17+
suppressRootClose(event) {
18+
// Tag the native event to prevent the root close logic on document click.
19+
// This seems safer than using event.nativeEvent.stopImmediatePropagation(),
20+
// which is only supported in IE >= 9.
21+
event.nativeEvent[id] = true;
22+
}
23+
};
1624
}
1725

1826
export default class RootCloseWrapper extends React.Component {
@@ -21,6 +29,11 @@ export default class RootCloseWrapper extends React.Component {
2129

2230
this.handleDocumentClick = this.handleDocumentClick.bind(this);
2331
this.handleDocumentKeyUp = this.handleDocumentKeyUp.bind(this);
32+
33+
let { id, suppressRootClose } = getSuppressRootClose();
34+
35+
this._suppressRootId = id;
36+
this._suppressRootClosehHandler = suppressRootClose;
2437
}
2538

2639
bindRootCloseHandlers() {
@@ -35,7 +48,7 @@ export default class RootCloseWrapper extends React.Component {
3548

3649
handleDocumentClick(e) {
3750
// This is now the native event.
38-
if (e[CLICK_WAS_INSIDE]) {
51+
if (e[this._suppressRootId]) {
3952
return;
4053
}
4154

@@ -68,14 +81,14 @@ export default class RootCloseWrapper extends React.Component {
6881

6982
if (noWrap) {
7083
return React.cloneElement(child, {
71-
onClick: createChainedFunction(suppressRootClose, child.props.onClick)
84+
onClick: createChainedFunction(this._suppressRootClosehHandler, child.props.onClick)
7285
});
7386
}
7487

7588
// Wrap the child in a new element, so the child won't have to handle
7689
// potentially combining multiple onClick listeners.
7790
return (
78-
<div onClick={suppressRootClose}>
91+
<div onClick={this._suppressRootClosehHandler}>
7992
{child}
8093
</div>
8194
);

test/RootCloseSpec.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import RootCloseWrapper from '../src/RootCloseWrapper';
4+
import { render } from './helpers';
5+
import simulant from 'simulant';
6+
7+
describe('RootCloseWrapper', function () {
8+
let mountPoint;
9+
10+
beforeEach(()=>{
11+
mountPoint = document.createElement('div');
12+
document.body.appendChild(mountPoint);
13+
});
14+
15+
afterEach(function () {
16+
ReactDOM.unmountComponentAtNode(mountPoint);
17+
document.body.removeChild(mountPoint);
18+
});
19+
20+
it('should close when clicked outside', () => {
21+
let spy = sinon.spy();
22+
render(
23+
<RootCloseWrapper onRootClose={spy}>
24+
<div id='my-div'>hello there</div>
25+
</RootCloseWrapper>
26+
, mountPoint);
27+
28+
simulant.fire(document.getElementById('my-div'), 'click');
29+
30+
expect(spy).to.not.have.been.called;
31+
32+
simulant.fire(document.body, 'click');
33+
34+
expect(spy).to.have.been.calledOnce;
35+
});
36+
37+
it('should close when inside another RootCloseWrapper', () => {
38+
let outerSpy = sinon.spy();
39+
let innerSpy = sinon.spy();
40+
41+
render(
42+
<RootCloseWrapper onRootClose={outerSpy}>
43+
<div>
44+
<div id='my-div'>hello there</div>
45+
<RootCloseWrapper onRootClose={innerSpy}>
46+
<div id='my-other-div'>hello there</div>
47+
</RootCloseWrapper>
48+
</div>
49+
</RootCloseWrapper>
50+
, mountPoint);
51+
52+
simulant.fire(document.getElementById('my-div'), 'click');
53+
54+
expect(outerSpy).to.have.not.been.called;
55+
expect(innerSpy).to.have.been.calledOnce;
56+
});
57+
58+
});

0 commit comments

Comments
 (0)