UNPKG

react-bootstrap

Version:

Bootstrap 3 components build with React

227 lines (190 loc) 5.71 kB
var React = require('react'); var OverlayMixin = require('./OverlayMixin'); var domUtils = require('./utils/domUtils'); var cloneWithProps = require('./utils/cloneWithProps'); var createChainedFunction = require('./utils/createChainedFunction'); var assign = require('./utils/Object.assign'); /** * Check if value one is inside or equal to the of value * * @param {string} one * @param {string|array} of * @returns {boolean} */ function isOneOf(one, of) { if (Array.isArray(of)) { return of.indexOf(one) >= 0; } return one === of; } var OverlayTrigger = React.createClass({ mixins: [OverlayMixin], propTypes: { trigger: React.PropTypes.oneOfType([ React.PropTypes.oneOf(['manual', 'click', 'hover', 'focus']), React.PropTypes.arrayOf(React.PropTypes.oneOf(['click', 'hover', 'focus'])) ]), placement: React.PropTypes.oneOf(['top','right', 'bottom', 'left']), delay: React.PropTypes.number, delayShow: React.PropTypes.number, delayHide: React.PropTypes.number, defaultOverlayShown: React.PropTypes.bool, overlay: React.PropTypes.node.isRequired }, getDefaultProps: function () { return { placement: 'right', trigger: ['hover', 'focus'] }; }, getInitialState: function () { return { isOverlayShown: this.props.defaultOverlayShown == null ? false : this.props.defaultOverlayShown, overlayLeft: null, overlayTop: null }; }, show: function () { this.setState({ isOverlayShown: true }, function() { this.updateOverlayPosition(); }); }, hide: function () { this.setState({ isOverlayShown: false }); }, toggle: function () { this.state.isOverlayShown ? this.hide() : this.show(); }, renderOverlay: function () { if (!this.state.isOverlayShown) { return <span />; } return cloneWithProps( this.props.overlay, { onRequestHide: this.hide, placement: this.props.placement, positionLeft: this.state.overlayLeft, positionTop: this.state.overlayTop } ); }, render: function () { if (this.props.trigger === 'manual') { return React.Children.only(this.props.children); } var props = {}; if (isOneOf('click', this.props.trigger)) { props.onClick = createChainedFunction(this.toggle, this.props.onClick); } if (isOneOf('hover', this.props.trigger)) { props.onMouseOver = createChainedFunction(this.handleDelayedShow, this.props.onMouseOver); props.onMouseOut = createChainedFunction(this.handleDelayedHide, this.props.onMouseOut); } if (isOneOf('focus', this.props.trigger)) { props.onFocus = createChainedFunction(this.handleDelayedShow, this.props.onFocus); props.onBlur = createChainedFunction(this.handleDelayedHide, this.props.onBlur); } return cloneWithProps( React.Children.only(this.props.children), props ); }, componentWillUnmount: function() { clearTimeout(this._hoverDelay); }, componentDidMount: function() { this.updateOverlayPosition(); }, handleDelayedShow: function () { if (this._hoverDelay != null) { clearTimeout(this._hoverDelay); this._hoverDelay = null; return; } var delay = this.props.delayShow != null ? this.props.delayShow : this.props.delay; if (!delay) { this.show(); return; } this._hoverDelay = setTimeout(function() { this._hoverDelay = null; this.show(); }.bind(this), delay); }, handleDelayedHide: function () { if (this._hoverDelay != null) { clearTimeout(this._hoverDelay); this._hoverDelay = null; return; } var delay = this.props.delayHide != null ? this.props.delayHide : this.props.delay; if (!delay) { this.hide(); return; } this._hoverDelay = setTimeout(function() { this._hoverDelay = null; this.hide(); }.bind(this), delay); }, updateOverlayPosition: function () { if (!this.isMounted()) { return; } var pos = this.calcOverlayPosition(); this.setState({ overlayLeft: pos.left, overlayTop: pos.top }); }, calcOverlayPosition: function () { var childOffset = this.getPosition(); var overlayNode = this.getOverlayDOMNode(); var overlayHeight = overlayNode.offsetHeight; var overlayWidth = overlayNode.offsetWidth; switch (this.props.placement) { case 'right': return { top: childOffset.top + childOffset.height / 2 - overlayHeight / 2, left: childOffset.left + childOffset.width }; case 'left': return { top: childOffset.top + childOffset.height / 2 - overlayHeight / 2, left: childOffset.left - overlayWidth }; case 'top': return { top: childOffset.top - overlayHeight, left: childOffset.left + childOffset.width / 2 - overlayWidth / 2 }; case 'bottom': return { top: childOffset.top + childOffset.height, left: childOffset.left + childOffset.width / 2 - overlayWidth / 2 }; default: throw new Error('calcOverlayPosition(): No such placement of "' + this.props.placement + '" found.'); } }, getPosition: function () { var node = this.getDOMNode(); var container = this.getContainerDOMNode(); var offset = container.tagName == 'BODY' ? domUtils.getOffset(node) : domUtils.getPosition(node, container); return assign({}, offset, { height: node.offsetHeight, width: node.offsetWidth }); } }); module.exports = OverlayTrigger;