UNPKG

12.5 kBJavaScriptView Raw
1import _extends from 'babel-runtime/helpers/extends';
2import _objectWithoutProperties from 'babel-runtime/helpers/objectWithoutProperties';
3import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
4import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
5import _inherits from 'babel-runtime/helpers/inherits';
6import classNames from 'classnames';
7import React, { cloneElement } from 'react';
8import PropTypes from 'prop-types';
9
10import CarouselCaption from './CarouselCaption';
11import CarouselItem from './CarouselItem';
12import Glyphicon from './Glyphicon';
13import SafeAnchor from './SafeAnchor';
14import { bsClass, getClassSet, prefix, splitBsPropsAndOmit } from './utils/bootstrapUtils';
15import ValidComponentChildren from './utils/ValidComponentChildren';
16
17// TODO: `slide` should be `animate`.
18
19// TODO: Use uncontrollable.
20
21var propTypes = {
22 slide: PropTypes.bool,
23 indicators: PropTypes.bool,
24 /**
25 * The amount of time to delay between automatically cycling an item.
26 * If `null`, carousel will not automatically cycle.
27 */
28 interval: PropTypes.number,
29 controls: PropTypes.bool,
30 pauseOnHover: PropTypes.bool,
31 wrap: PropTypes.bool,
32 /**
33 * Callback fired when the active item changes.
34 *
35 * ```js
36 * (eventKey: any, ?event: Object) => any
37 * ```
38 *
39 * If this callback takes two or more arguments, the second argument will
40 * be a persisted event object with `direction` set to the direction of the
41 * transition.
42 */
43 onSelect: PropTypes.func,
44 onSlideEnd: PropTypes.func,
45 activeIndex: PropTypes.number,
46 defaultActiveIndex: PropTypes.number,
47 direction: PropTypes.oneOf(['prev', 'next']),
48 prevIcon: PropTypes.node,
49 /**
50 * Label shown to screen readers only, can be used to show the previous element
51 * in the carousel.
52 * Set to null to deactivate.
53 */
54 prevLabel: PropTypes.string,
55 nextIcon: PropTypes.node,
56 /**
57 * Label shown to screen readers only, can be used to show the next element
58 * in the carousel.
59 * Set to null to deactivate.
60 */
61 nextLabel: PropTypes.string
62};
63
64var defaultProps = {
65 slide: true,
66 interval: 5000,
67 pauseOnHover: true,
68 wrap: true,
69 indicators: true,
70 controls: true,
71 prevIcon: React.createElement(Glyphicon, { glyph: 'chevron-left' }),
72 prevLabel: 'Previous',
73 nextIcon: React.createElement(Glyphicon, { glyph: 'chevron-right' }),
74 nextLabel: 'Next'
75};
76
77var Carousel = function (_React$Component) {
78 _inherits(Carousel, _React$Component);
79
80 function Carousel(props, context) {
81 _classCallCheck(this, Carousel);
82
83 var _this = _possibleConstructorReturn(this, _React$Component.call(this, props, context));
84
85 _this.handleMouseOver = _this.handleMouseOver.bind(_this);
86 _this.handleMouseOut = _this.handleMouseOut.bind(_this);
87 _this.handlePrev = _this.handlePrev.bind(_this);
88 _this.handleNext = _this.handleNext.bind(_this);
89 _this.handleItemAnimateOutEnd = _this.handleItemAnimateOutEnd.bind(_this);
90
91 var defaultActiveIndex = props.defaultActiveIndex;
92
93
94 _this.state = {
95 activeIndex: defaultActiveIndex != null ? defaultActiveIndex : 0,
96 previousActiveIndex: null,
97 direction: null
98 };
99
100 _this.isUnmounted = false;
101 return _this;
102 }
103
104 Carousel.prototype.componentDidMount = function componentDidMount() {
105 this.waitForNext();
106 };
107
108 Carousel.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
109 var activeIndex = this.getActiveIndex();
110
111 if (nextProps.activeIndex != null && nextProps.activeIndex !== activeIndex) {
112 clearTimeout(this.timeout);
113
114 this.setState({
115 previousActiveIndex: activeIndex,
116 direction: nextProps.direction != null ? nextProps.direction : this.getDirection(activeIndex, nextProps.activeIndex)
117 });
118 }
119
120 if (nextProps.activeIndex == null && this.state.activeIndex >= nextProps.children.length) {
121 this.setState({
122 activeIndex: 0,
123 previousActiveIndex: null,
124 direction: null
125 });
126 }
127 };
128
129 Carousel.prototype.componentWillUnmount = function componentWillUnmount() {
130 clearTimeout(this.timeout);
131 this.isUnmounted = true;
132 };
133
134 Carousel.prototype.getActiveIndex = function getActiveIndex() {
135 var activeIndexProp = this.props.activeIndex;
136 return activeIndexProp != null ? activeIndexProp : this.state.activeIndex;
137 };
138
139 Carousel.prototype.getDirection = function getDirection(prevIndex, index) {
140 if (prevIndex === index) {
141 return null;
142 }
143
144 return prevIndex > index ? 'prev' : 'next';
145 };
146
147 Carousel.prototype.handleItemAnimateOutEnd = function handleItemAnimateOutEnd() {
148 var _this2 = this;
149
150 this.setState({
151 previousActiveIndex: null,
152 direction: null
153 }, function () {
154 _this2.waitForNext();
155
156 if (_this2.props.onSlideEnd) {
157 _this2.props.onSlideEnd();
158 }
159 });
160 };
161
162 Carousel.prototype.handleMouseOut = function handleMouseOut() {
163 if (this.isPaused) {
164 this.play();
165 }
166 };
167
168 Carousel.prototype.handleMouseOver = function handleMouseOver() {
169 if (this.props.pauseOnHover) {
170 this.pause();
171 }
172 };
173
174 Carousel.prototype.handleNext = function handleNext(e) {
175 var index = this.getActiveIndex() + 1;
176 var count = ValidComponentChildren.count(this.props.children);
177
178 if (index > count - 1) {
179 if (!this.props.wrap) {
180 return;
181 }
182 index = 0;
183 }
184
185 this.select(index, e, 'next');
186 };
187
188 Carousel.prototype.handlePrev = function handlePrev(e) {
189 var index = this.getActiveIndex() - 1;
190
191 if (index < 0) {
192 if (!this.props.wrap) {
193 return;
194 }
195 index = ValidComponentChildren.count(this.props.children) - 1;
196 }
197
198 this.select(index, e, 'prev');
199 };
200
201 // This might be a public API.
202
203
204 Carousel.prototype.pause = function pause() {
205 this.isPaused = true;
206 clearTimeout(this.timeout);
207 };
208
209 // This might be a public API.
210
211
212 Carousel.prototype.play = function play() {
213 this.isPaused = false;
214 this.waitForNext();
215 };
216
217 Carousel.prototype.select = function select(index, e, direction) {
218 clearTimeout(this.timeout);
219
220 // TODO: Is this necessary? Seems like the only risk is if the component
221 // unmounts while handleItemAnimateOutEnd fires.
222 if (this.isUnmounted) {
223 return;
224 }
225
226 var previousActiveIndex = this.props.slide ? this.getActiveIndex() : null;
227 direction = direction || this.getDirection(previousActiveIndex, index);
228
229 var onSelect = this.props.onSelect;
230
231
232 if (onSelect) {
233 if (onSelect.length > 1) {
234 // React SyntheticEvents are pooled, so we need to remove this event
235 // from the pool to add a custom property. To avoid unnecessarily
236 // removing objects from the pool, only do this when the listener
237 // actually wants the event.
238 if (e) {
239 e.persist();
240 e.direction = direction;
241 } else {
242 e = { direction: direction };
243 }
244
245 onSelect(index, e);
246 } else {
247 onSelect(index);
248 }
249 }
250
251 if (this.props.activeIndex == null && index !== previousActiveIndex) {
252 if (this.state.previousActiveIndex != null) {
253 // If currently animating don't activate the new index.
254 // TODO: look into queueing this canceled call and
255 // animating after the current animation has ended.
256 return;
257 }
258
259 this.setState({
260 activeIndex: index,
261 previousActiveIndex: previousActiveIndex,
262 direction: direction
263 });
264 }
265 };
266
267 Carousel.prototype.waitForNext = function waitForNext() {
268 var _props = this.props,
269 slide = _props.slide,
270 interval = _props.interval,
271 activeIndexProp = _props.activeIndex;
272
273
274 if (!this.isPaused && slide && interval && activeIndexProp == null) {
275 this.timeout = setTimeout(this.handleNext, interval);
276 }
277 };
278
279 Carousel.prototype.renderControls = function renderControls(properties) {
280 var wrap = properties.wrap,
281 children = properties.children,
282 activeIndex = properties.activeIndex,
283 prevIcon = properties.prevIcon,
284 nextIcon = properties.nextIcon,
285 bsProps = properties.bsProps,
286 prevLabel = properties.prevLabel,
287 nextLabel = properties.nextLabel;
288
289 var controlClassName = prefix(bsProps, 'control');
290 var count = ValidComponentChildren.count(children);
291
292 return [(wrap || activeIndex !== 0) && React.createElement(
293 SafeAnchor,
294 {
295 key: 'prev',
296 className: classNames(controlClassName, 'left'),
297 onClick: this.handlePrev
298 },
299 prevIcon,
300 prevLabel && React.createElement(
301 'span',
302 { className: 'sr-only' },
303 prevLabel
304 )
305 ), (wrap || activeIndex !== count - 1) && React.createElement(
306 SafeAnchor,
307 {
308 key: 'next',
309 className: classNames(controlClassName, 'right'),
310 onClick: this.handleNext
311 },
312 nextIcon,
313 nextLabel && React.createElement(
314 'span',
315 { className: 'sr-only' },
316 nextLabel
317 )
318 )];
319 };
320
321 Carousel.prototype.renderIndicators = function renderIndicators(children, activeIndex, bsProps) {
322 var _this3 = this;
323
324 var indicators = [];
325
326 ValidComponentChildren.forEach(children, function (child, index) {
327 indicators.push(React.createElement('li', {
328 key: index,
329 className: index === activeIndex ? 'active' : null,
330 onClick: function onClick(e) {
331 return _this3.select(index, e);
332 }
333 }),
334
335 // Force whitespace between indicator elements. Bootstrap requires
336 // this for correct spacing of elements.
337 ' ');
338 });
339
340 return React.createElement(
341 'ol',
342 { className: prefix(bsProps, 'indicators') },
343 indicators
344 );
345 };
346
347 Carousel.prototype.render = function render() {
348 var _this4 = this;
349
350 var _props2 = this.props,
351 slide = _props2.slide,
352 indicators = _props2.indicators,
353 controls = _props2.controls,
354 wrap = _props2.wrap,
355 prevIcon = _props2.prevIcon,
356 prevLabel = _props2.prevLabel,
357 nextIcon = _props2.nextIcon,
358 nextLabel = _props2.nextLabel,
359 className = _props2.className,
360 children = _props2.children,
361 props = _objectWithoutProperties(_props2, ['slide', 'indicators', 'controls', 'wrap', 'prevIcon', 'prevLabel', 'nextIcon', 'nextLabel', 'className', 'children']);
362
363 var _state = this.state,
364 previousActiveIndex = _state.previousActiveIndex,
365 direction = _state.direction;
366
367 var _splitBsPropsAndOmit = splitBsPropsAndOmit(props, ['interval', 'pauseOnHover', 'onSelect', 'onSlideEnd', 'activeIndex', // Accessed via this.getActiveIndex().
368 'defaultActiveIndex', 'direction']),
369 bsProps = _splitBsPropsAndOmit[0],
370 elementProps = _splitBsPropsAndOmit[1];
371
372 var activeIndex = this.getActiveIndex();
373
374 var classes = _extends({}, getClassSet(bsProps), {
375 slide: slide
376 });
377
378 return React.createElement(
379 'div',
380 _extends({}, elementProps, {
381 className: classNames(className, classes),
382 onMouseOver: this.handleMouseOver,
383 onMouseOut: this.handleMouseOut
384 }),
385 indicators && this.renderIndicators(children, activeIndex, bsProps),
386 React.createElement(
387 'div',
388 { className: prefix(bsProps, 'inner') },
389 ValidComponentChildren.map(children, function (child, index) {
390 var active = index === activeIndex;
391 var previousActive = slide && index === previousActiveIndex;
392
393 return cloneElement(child, {
394 active: active,
395 index: index,
396 animateOut: previousActive,
397 animateIn: active && previousActiveIndex != null && slide,
398 direction: direction,
399 onAnimateOutEnd: previousActive ? _this4.handleItemAnimateOutEnd : null
400 });
401 })
402 ),
403 controls && this.renderControls({
404 wrap: wrap,
405 children: children,
406 activeIndex: activeIndex,
407 prevIcon: prevIcon,
408 prevLabel: prevLabel,
409 nextIcon: nextIcon,
410 nextLabel: nextLabel,
411 bsProps: bsProps
412 })
413 );
414 };
415
416 return Carousel;
417}(React.Component);
418
419Carousel.propTypes = propTypes;
420Carousel.defaultProps = defaultProps;
421
422Carousel.Caption = CarouselCaption;
423Carousel.Item = CarouselItem;
424
425export default bsClass('carousel', Carousel);
\No newline at end of file