Skip to content

Commit 191f2ee

Browse files
committed
Merge pull request react-bootstrap#1017 from AlexKVal/tabbedArea
Tabbed area. Fix for animated panes rendering.
2 parents 952798b + 2558f32 commit 191f2ee

File tree

2 files changed

+76
-16
lines changed

2 files changed

+76
-16
lines changed

src/TabbedArea.js

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,22 @@ const TabbedArea = React.createClass({
5151

5252
componentWillReceiveProps(nextProps) {
5353
if (nextProps.activeKey != null && nextProps.activeKey !== this.props.activeKey) {
54+
// check if the 'previousActiveKey' child still exists
55+
let previousActiveKey = this.props.activeKey;
56+
React.Children.forEach(nextProps.children, (child) => {
57+
if (React.isValidElement(child)) {
58+
if (child.props.eventKey === previousActiveKey) {
59+
this.setState({
60+
previousActiveKey
61+
});
62+
return;
63+
}
64+
}
65+
});
66+
67+
// if the 'previousActiveKey' child does not exist anymore
5468
this.setState({
55-
previousActiveKey: this.props.activeKey
69+
previousActiveKey: null
5670
});
5771
}
5872
},
@@ -66,15 +80,12 @@ const TabbedArea = React.createClass({
6680
render() {
6781
let { id, ...props } = this.props;
6882

69-
let activeKey =
70-
this.props.activeKey != null ? this.props.activeKey : this.state.activeKey;
71-
7283
function renderTabIfSet(child) {
7384
return child.props.tab != null ? this.renderTab(child) : null;
7485
}
7586

7687
let nav = (
77-
<Nav {...props} activeKey={activeKey} onSelect={this.handleSelect} ref="tabs">
88+
<Nav {...props} activeKey={this.getActiveKey()} onSelect={this.handleSelect} ref="tabs">
7889
{ValidComponentChildren.map(this.props.children, renderTabIfSet, this)}
7990
</Nav>
8091
);
@@ -94,21 +105,22 @@ const TabbedArea = React.createClass({
94105
},
95106

96107
renderPane(child, index) {
97-
let activeKey = this.getActiveKey();
108+
let previousActiveKey = this.state.previousActiveKey;
98109

99-
let active = (child.props.eventKey === activeKey &&
100-
(this.state.previousActiveKey == null || !this.props.animation));
110+
let shouldPaneBeSetActive = child.props.eventKey === this.getActiveKey();
111+
let thereIsNoActivePane = previousActiveKey == null;
112+
113+
let paneIsAlreadyActive = previousActiveKey != null && child.props.eventKey === previousActiveKey;
101114

102115
return cloneElement(
103116
child,
104117
{
105-
active,
118+
active: shouldPaneBeSetActive && (thereIsNoActivePane || !this.props.animation),
106119
id: panelId(this.props, child),
107120
'aria-labelledby': tabId(this.props, child),
108121
key: child.key ? child.key : index,
109122
animation: this.props.animation,
110-
onAnimateOutEnd: (this.state.previousActiveKey != null &&
111-
child.props.eventKey === this.state.previousActiveKey) ? this.handlePaneAnimateOutEnd : null
123+
onAnimateOutEnd: paneIsAlreadyActive ? this.handlePaneAnimateOutEnd : null
112124
}
113125
);
114126
},
@@ -134,15 +146,20 @@ const TabbedArea = React.createClass({
134146
return !this._isChanging;
135147
},
136148

137-
handleSelect(key) {
149+
handleSelect(selectedKey) {
138150
if (this.props.onSelect) {
139151
this._isChanging = true;
140-
this.props.onSelect(key);
152+
this.props.onSelect(selectedKey);
141153
this._isChanging = false;
142-
} else if (key !== this.getActiveKey()) {
154+
return;
155+
}
156+
157+
// if there is no external handler, then use embedded one
158+
let previousActiveKey = this.getActiveKey();
159+
if (selectedKey !== previousActiveKey) {
143160
this.setState({
144-
activeKey: key,
145-
previousActiveKey: this.getActiveKey()
161+
activeKey: selectedKey,
162+
previousActiveKey
146163
});
147164
}
148165
}

test/TabbedAreaSpec.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import TabbedArea from '../src/TabbedArea';
44
import NavItem from '../src/NavItem';
55
import TabPane from '../src/TabPane';
66
import ValidComponentChildren from '../src/utils/ValidComponentChildren';
7+
import { render } from './helpers';
78

89
describe('TabbedArea', function () {
910
it('Should show the correct tab', function () {
@@ -230,6 +231,48 @@ describe('TabbedArea', function () {
230231
assert.equal(tabbedArea.refs.tabs.props.activeKey, 2);
231232
});
232233

234+
describe('animation', function () {
235+
let mountPoint;
236+
237+
beforeEach(()=>{
238+
mountPoint = document.createElement('div');
239+
document.body.appendChild(mountPoint);
240+
});
241+
242+
afterEach(function () {
243+
React.unmountComponentAtNode(mountPoint);
244+
document.body.removeChild(mountPoint);
245+
});
246+
247+
function checkTabRemovingWithAnimation(animation) {
248+
it(`should correctly set "active" after tabPane is removed with "animation=${animation}"`, function() {
249+
let instance = render(
250+
<TabbedArea activeKey={2} animation={animation}>
251+
<TabPane tab="Tab 1" eventKey={1}>Tab 1 content</TabPane>
252+
<TabPane tab="Tab 2" eventKey={2}>Tab 2 content</TabPane>
253+
</TabbedArea>
254+
, mountPoint);
255+
256+
let panes = ReactTestUtils.scryRenderedComponentsWithType(instance, TabPane);
257+
258+
assert.equal(panes[0].props.active, false);
259+
assert.equal(panes[1].props.active, true);
260+
261+
// second tab has been removed
262+
render(
263+
<TabbedArea activeKey={1} animation={animation}>
264+
<TabPane tab="Tab 1" eventKey={1}>Tab 1 content</TabPane>
265+
</TabbedArea>
266+
, mountPoint);
267+
268+
assert.equal(panes[0].props.active, true);
269+
});
270+
}
271+
272+
checkTabRemovingWithAnimation(true);
273+
checkTabRemovingWithAnimation(false);
274+
});
275+
233276
describe('Web Accessibility', function(){
234277

235278
it('Should generate ids from parent id', function () {

0 commit comments

Comments
 (0)