@talend/react-bootstrap
Version:
Bootstrap 3 components built with React
344 lines (339 loc) • 10.2 kB
JavaScript
import classNames from 'classnames';
import React, { cloneElement } from 'react';
import PropTypes from 'prop-types';
import CarouselCaption from './CarouselCaption';
import CarouselItem from './CarouselItem';
import Glyphicon from './Glyphicon';
import SafeAnchor from './SafeAnchor';
import { bsClass, getClassSet, prefix, splitBsPropsAndOmit } from './utils/bootstrapUtils';
import ValidComponentChildren from './utils/ValidComponentChildren';
// TODO: `slide` should be `animate`.
// TODO: Use uncontrollable.
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const propTypes = {
slide: PropTypes.bool,
indicators: PropTypes.bool,
/**
* The amount of time to delay between automatically cycling an item.
* If `null`, carousel will not automatically cycle.
*/
interval: PropTypes.number,
controls: PropTypes.bool,
pauseOnHover: PropTypes.bool,
wrap: PropTypes.bool,
/**
* Callback fired when the active item changes.
*
* ```js
* (eventKey: any, ?event: Object) => any
* ```
*
* If this callback takes two or more arguments, the second argument will
* be a persisted event object with `direction` set to the direction of the
* transition.
*/
onSelect: PropTypes.func,
onSlideEnd: PropTypes.func,
activeIndex: PropTypes.number,
defaultActiveIndex: PropTypes.number,
direction: PropTypes.oneOf(['prev', 'next']),
prevIcon: PropTypes.node,
/**
* Label shown to screen readers only, can be used to show the previous element
* in the carousel.
* Set to null to deactivate.
*/
prevLabel: PropTypes.string,
nextIcon: PropTypes.node,
/**
* Label shown to screen readers only, can be used to show the next element
* in the carousel.
* Set to null to deactivate.
*/
nextLabel: PropTypes.string
};
const defaultProps = {
slide: true,
interval: 5000,
pauseOnHover: true,
wrap: true,
indicators: true,
controls: true,
prevIcon: /*#__PURE__*/_jsx(Glyphicon, {
glyph: "chevron-left"
}),
prevLabel: 'Previous',
nextIcon: /*#__PURE__*/_jsx(Glyphicon, {
glyph: "chevron-right"
}),
nextLabel: 'Next'
};
class Carousel extends React.Component {
constructor(props, context) {
super(props, context);
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseOut = this.handleMouseOut.bind(this);
this.handlePrev = this.handlePrev.bind(this);
this.handleNext = this.handleNext.bind(this);
this.handleItemAnimateOutEnd = this.handleItemAnimateOutEnd.bind(this);
const {
defaultActiveIndex
} = props;
this.state = {
activeIndex: defaultActiveIndex != null ? defaultActiveIndex : 0,
previousActiveIndex: null,
direction: null
};
this.isUnmounted = false;
}
componentDidMount() {
this.waitForNext();
}
componentDidUpdate(prevProps) {
// eslint-disable-line
const activeIndex = this.getActiveIndex();
if (this.props.activeIndex != null && this.props.activeIndex !== activeIndex) {
clearTimeout(this.timeout);
this.setState({
previousActiveIndex: activeIndex,
direction: this.props.direction != null ? this.props.direction : this.getDirection(activeIndex, this.props.activeIndex)
});
}
if (this.props.activeIndex == null && this.state.activeIndex >= this.props.children.length) {
this.setState({
activeIndex: 0,
previousActiveIndex: null,
direction: null
});
}
}
componentWillUnmount() {
clearTimeout(this.timeout);
this.isUnmounted = true;
}
getActiveIndex() {
const activeIndexProp = this.props.activeIndex;
return activeIndexProp != null ? activeIndexProp : this.state.activeIndex;
}
getDirection(prevIndex, index) {
if (prevIndex === index) {
return null;
}
return prevIndex > index ? 'prev' : 'next';
}
handleItemAnimateOutEnd() {
this.setState({
previousActiveIndex: null,
direction: null
}, () => {
this.waitForNext();
if (this.props.onSlideEnd) {
this.props.onSlideEnd();
}
});
}
handleMouseOut() {
if (this.isPaused) {
this.play();
}
}
handleMouseOver() {
if (this.props.pauseOnHover) {
this.pause();
}
}
handleNext(e) {
let index = this.getActiveIndex() + 1;
const count = ValidComponentChildren.count(this.props.children);
if (index > count - 1) {
if (!this.props.wrap) {
return;
}
index = 0;
}
this.select(index, e, 'next');
}
handlePrev(e) {
let index = this.getActiveIndex() - 1;
if (index < 0) {
if (!this.props.wrap) {
return;
}
index = ValidComponentChildren.count(this.props.children) - 1;
}
this.select(index, e, 'prev');
}
// This might be a public API.
pause() {
this.isPaused = true;
clearTimeout(this.timeout);
}
// This might be a public API.
play() {
this.isPaused = false;
this.waitForNext();
}
select(index, e, direction) {
clearTimeout(this.timeout);
// TODO: Is this necessary? Seems like the only risk is if the component
// unmounts while handleItemAnimateOutEnd fires.
if (this.isUnmounted) {
return;
}
const previousActiveIndex = this.props.slide ? this.getActiveIndex() : null;
direction = direction || this.getDirection(previousActiveIndex, index);
const {
onSelect
} = this.props;
if (onSelect) {
if (onSelect.length > 1) {
// React SyntheticEvents are pooled, so we need to remove this event
// from the pool to add a custom property. To avoid unnecessarily
// removing objects from the pool, only do this when the listener
// actually wants the event.
if (e) {
e.persist();
e.direction = direction;
} else {
e = {
direction
};
}
onSelect(index, e);
} else {
onSelect(index);
}
}
if (this.props.activeIndex == null && index !== previousActiveIndex) {
if (this.state.previousActiveIndex != null) {
// If currently animating don't activate the new index.
// TODO: look into queueing this canceled call and
// animating after the current animation has ended.
return;
}
this.setState({
activeIndex: index,
previousActiveIndex,
direction
});
}
}
waitForNext() {
const {
slide,
interval,
activeIndex: activeIndexProp
} = this.props;
if (!this.isPaused && slide && interval && activeIndexProp == null) {
this.timeout = setTimeout(this.handleNext, interval);
}
}
renderControls(properties) {
const {
wrap,
children,
activeIndex,
prevIcon,
nextIcon,
bsProps,
prevLabel,
nextLabel
} = properties;
const controlClassName = prefix(bsProps, 'control');
const count = ValidComponentChildren.count(children);
return [(wrap || activeIndex !== 0) && /*#__PURE__*/_jsxs(SafeAnchor, {
className: classNames(controlClassName, 'left'),
onClick: this.handlePrev,
children: [prevIcon, prevLabel && /*#__PURE__*/_jsx("span", {
className: "sr-only",
children: prevLabel
})]
}, "prev"), (wrap || activeIndex !== count - 1) && /*#__PURE__*/_jsxs(SafeAnchor, {
className: classNames(controlClassName, 'right'),
onClick: this.handleNext,
children: [nextIcon, nextLabel && /*#__PURE__*/_jsx("span", {
className: "sr-only",
children: nextLabel
})]
}, "next")];
}
renderIndicators(children, activeIndex, bsProps) {
let indicators = [];
ValidComponentChildren.forEach(children, (child, index) => {
indicators.push(/*#__PURE__*/_jsx("li", {
className: index === activeIndex ? 'active' : null,
onClick: e => this.select(index, e)
}, index),
// Force whitespace between indicator elements. Bootstrap requires
// this for correct spacing of elements.
' ');
});
return /*#__PURE__*/_jsx("ol", {
className: prefix(bsProps, 'indicators'),
children: indicators
});
}
render() {
const {
slide,
indicators,
controls,
wrap,
prevIcon,
prevLabel,
nextIcon,
nextLabel,
className,
children,
...props
} = this.props;
const {
previousActiveIndex,
direction
} = this.state;
const [bsProps, elementProps] = splitBsPropsAndOmit(props, ['interval', 'pauseOnHover', 'onSelect', 'onSlideEnd', 'activeIndex',
// Accessed via this.getActiveIndex().
'defaultActiveIndex', 'direction']);
const activeIndex = this.getActiveIndex();
const classes = {
...getClassSet(bsProps),
slide
};
return /*#__PURE__*/_jsxs("div", {
...elementProps,
className: classNames(className, classes),
onMouseOver: this.handleMouseOver,
onMouseOut: this.handleMouseOut,
children: [indicators && this.renderIndicators(children, activeIndex, bsProps), /*#__PURE__*/_jsx("div", {
className: prefix(bsProps, 'inner'),
children: ValidComponentChildren.map(children, (child, index) => {
const active = index === activeIndex;
const previousActive = slide && index === previousActiveIndex;
return /*#__PURE__*/cloneElement(child, {
active,
index,
animateOut: previousActive,
animateIn: active && previousActiveIndex != null && slide,
direction,
onAnimateOutEnd: previousActive ? this.handleItemAnimateOutEnd : null
});
})
}), controls && this.renderControls({
wrap,
children,
activeIndex,
prevIcon,
prevLabel,
nextIcon,
nextLabel,
bsProps
})]
});
}
}
Carousel.propTypes = propTypes;
Carousel.defaultProps = defaultProps;
Carousel.Caption = CarouselCaption;
Carousel.Item = CarouselItem;
export default bsClass('carousel', Carousel);
//# sourceMappingURL=Carousel.js.map