UNPKG

boldr-ui

Version:

UI components for Boldr

506 lines (445 loc) 16.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _keys = require('babel-runtime/core-js/object/keys'); var _keys2 = _interopRequireDefault(_keys); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _get2 = require('babel-runtime/helpers/get'); var _get3 = _interopRequireDefault(_get2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _BoldrComponent2 = require('../utils/BoldrComponent'); var _BoldrComponent3 = _interopRequireDefault(_BoldrComponent2); var _TooltipContent = require('./TooltipContent'); var _TooltipContent2 = _interopRequireDefault(_TooltipContent); var _TooltipPosition = require('./TooltipPosition'); var _TooltipPosition2 = _interopRequireDefault(_TooltipPosition); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* eslint-disable react/no-find-dom-node */ var renderSubtreeIntoContainer = _reactDom2.default.unstable_renderSubtreeIntoContainer; var _ref4 = _react2.default.createElement('div', null); var Tooltip = function (_BoldrComponent) { (0, _inherits3.default)(Tooltip, _BoldrComponent); function Tooltip() { var _ref; var _temp, _this, _ret; (0, _classCallCheck3.default)(this, Tooltip); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = (0, _possibleConstructorReturn3.default)(this, (_ref = Tooltip.__proto__ || (0, _getPrototypeOf2.default)(Tooltip)).call.apply(_ref, [this].concat(args))), _this), _initialiseProps.call(_this), _temp), (0, _possibleConstructorReturn3.default)(_this, _ret); } (0, _createClass3.default)(Tooltip, [{ key: 'componentElements', value: function componentElements() { var elements = (0, _get3.default)(Tooltip.prototype.__proto__ || (0, _getPrototypeOf2.default)(Tooltip.prototype), 'componentElements', this).call(this); return this._mountNode ? elements.concat(this._mountNode) : elements; } }, { key: 'onClickOutside', value: function onClickOutside(e) { if (this.props.shouldCloseOnClickOutside) { this.hide(); } else if (this.props.onClickOutside) { this.props.onClickOutside && this.props.onClickOutside(e); } } }, { key: 'componentDidUpdate', value: function componentDidUpdate() { var _this2 = this; if (this._mountNode && this.state.visible) { var arrowPlacement = { top: 'bottom', left: 'right', right: 'left', bottom: 'top' }; var tooltip = _react2.default.createElement( _TooltipContent2.default, { onMouseEnter: function onMouseEnter() { return _this2._onTooltipContentEnter(); }, onMouseLeave: function onMouseLeave() { return _this2._onTooltipContentLeave(); }, ref: function ref(_ref2) { return _this2.tooltipContent = _ref2; }, theme: this.props.theme, bounce: this.props.bounce, arrowPlacement: arrowPlacement[this.props.placement], style: { zIndex: this.props.zIndex }, arrowStyle: this.state.arrowStyle, maxWidth: this.props.maxWidth, size: this.props.size, textAlign: this.props.textAlign }, this.props.content ); renderSubtreeIntoContainer(this, tooltip, this._mountNode); } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { (0, _get3.default)(Tooltip.prototype.__proto__ || (0, _getPrototypeOf2.default)(Tooltip.prototype), 'componentWillUnmount', this) && (0, _get3.default)(Tooltip.prototype.__proto__ || (0, _getPrototypeOf2.default)(Tooltip.prototype), 'componentWillUnmount', this).call(this); this._unmounted = true; this._getContainer() && this.hide(); } }, { key: 'componentWillMount', value: function componentWillMount() { (0, _get3.default)(Tooltip.prototype.__proto__ || (0, _getPrototypeOf2.default)(Tooltip.prototype), 'componentWillMount', this) && (0, _get3.default)(Tooltip.prototype.__proto__ || (0, _getPrototypeOf2.default)(Tooltip.prototype), 'componentWillMount', this).call(this); if (this.props.active) { this.show(); } } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { (0, _get3.default)(Tooltip.prototype.__proto__ || (0, _getPrototypeOf2.default)(Tooltip.prototype), 'componentWillReceiveProps', this) && (0, _get3.default)(Tooltip.prototype.__proto__ || (0, _getPrototypeOf2.default)(Tooltip.prototype), 'componentWillReceiveProps', this).call(this, nextProps); if (nextProps.active !== this.props.active) { if (this.state.visible && this.props.hideTrigger === 'custom') { if (!nextProps.active) { this.hide(); } } if (!this.state.visible && this.props.showTrigger === 'custom') { if (nextProps.active) { this.show(); } } } } }, { key: 'render', value: function render() { var _this3 = this; var child = this.props.children; if (child) { return (0, _react.cloneElement)(child, { ref: function ref(_ref3) { return _this3._childNode = _reactDom2.default.findDOMNode(_ref3); }, onClick: this._chainCallbacks(child.props ? child.props.onClick : null, this._onClick), onMouseEnter: this._chainCallbacks(child.props ? child.props.onMouseEnter : null, this._onMouseEnter), onMouseLeave: this._chainCallbacks(child.props ? child.props.onMouseLeave : null, this._onMouseLeave), onFocus: this._chainCallbacks(child.props ? child.props.onFocus : null, this._onFocus), onBlur: this._chainCallbacks(child.props ? child.props.onBlur : null, this._onBlur) }); } else { return _ref4; } } }, { key: '_getContainer', value: function _getContainer() { if (typeof document === 'undefined') { return null; } return this.props.appendToParent ? this._childNode.parentElement : document ? document.body : null; } }, { key: 'show', value: function show() { var _this4 = this; if (this.props.disabled) { return; } if (this._unmounted) { return; } this.setState({ hidden: false }); if (this._hideTimeout) { clearTimeout(this._hideTimeout); this._hideTimeout = null; } if (this._showTimeout) { return; } if (!this.state.visible) { this._showTimeout = setTimeout(function () { if (_this4.props.onShow) { _this4.props.onShow(); } _this4.setState({ visible: true }, function () { if (!_this4._mountNode) { _this4._mountNode = document.createElement('div'); _this4._getContainer() && _this4._getContainer().appendChild(_this4._mountNode); } _this4._showTimeout = null; var fw = 0; var sw = 0; do { _this4.componentDidUpdate(); var tooltipNode = _reactDom2.default.findDOMNode(_this4.tooltipContent); fw = _this4._getRect(tooltipNode).width; _this4._updatePosition(_this4.tooltipContent); sw = _this4._getRect(tooltipNode).width; } while (!_this4.props.appendToParent && fw !== sw); }); }, this.props.showDelay); } } }, { key: 'hide', value: function hide() { var _this5 = this; this.setState({ hidden: true }); if (this._showTimeout) { clearTimeout(this._showTimeout); this._showTimeout = null; } if (this._hideTimeout) { return; } if (this.state.visible) { this._hideTimeout = setTimeout(function () { if (_this5._mountNode) { _reactDom2.default.unmountComponentAtNode(_this5._mountNode); _this5._getContainer() && _this5._getContainer().removeChild(_this5._mountNode); _this5._mountNode = null; } _this5._hideTimeout = null; if (!_this5._unmounted) { _this5.setState({ visible: false }); } }, this._unmounted ? 0 : this.props.hideDelay); } } }, { key: '_hideOrShow', value: function _hideOrShow(event) { if (this.props.hideTrigger === event && !this.state.hidden) { this.hide(); } else if (this.props.showTrigger === event) { this.show(); } } }, { key: '_onBlur', value: function _onBlur() { this._hideOrShow('blur'); } }, { key: '_onFocus', value: function _onFocus() { this._hideOrShow('focus'); } }, { key: '_onClick', value: function _onClick() { this._hideOrShow('click'); } }, { key: '_onMouseEnter', value: function _onMouseEnter() { this._hideOrShow('mouseenter'); } }, { key: '_onMouseLeave', value: function _onMouseLeave() { this._hideOrShow('mouseleave'); } }, { key: '_calculatePosition', value: function _calculatePosition(ref, tooltipNode) { if (!ref || !tooltipNode) { return { top: -1, left: -1 }; } return this._adjustPosition((0, _TooltipPosition2.default)(this._getRect(this._childNode), this._getRect(tooltipNode), { placement: this.props.placement, alignment: this.props.alignment, margin: 10 }, this.props.relative)); } }, { key: '_updatePosition', value: function _updatePosition(ref) { if (ref && this._childNode) { var tooltipNode = _reactDom2.default.findDOMNode(ref); var style = this._calculatePosition(ref, tooltipNode); if (this.props.relative) { tooltipNode.style.top = style.top + 'px'; tooltipNode.style.left = style.left + 'px'; } else { tooltipNode.style.top = style.top + 'px'; tooltipNode.style.left = Math.max(style.left, 0) + 'px'; } var arrowStyles = this._adjustArrowPosition(this.props.placement, this.props.moveArrowTo); if ((0, _keys2.default)(arrowStyles).length) { var arrow = tooltipNode.querySelector('.boldrui-tooltip__arrow'); arrow && (0, _keys2.default)(arrowStyles).forEach(function (key) { arrow.style[key] = arrowStyles[key]; }); } } } }, { key: '_adjustArrowPosition', value: function _adjustArrowPosition(placement, moveTo) { if (moveTo) { var isPositive = moveTo > 0; var pixels = isPositive ? moveTo : -moveTo; if (['top', 'bottom'].includes(placement)) { return isPositive ? { left: pixels + 'px' } : { left: 'auto', right: pixels + 'px' }; } return isPositive ? { top: pixels + 'px' } : { top: 'auto', bottom: pixels + 'px' }; } return {}; } }, { key: '_getRect', value: function _getRect(el) { if (this.props.appendToParent) { // TODO: Once thoroughly tested, we could use the same approach in both cases. return { left: el.offsetLeft, top: el.offsetTop, width: el.offsetWidth, height: el.offsetHeight }; } return el.getBoundingClientRect(); } }, { key: '_adjustPosition', value: function _adjustPosition(originalPosition) { var _ref5 = this.props.moveBy || {}, _ref5$x = _ref5.x, x = _ref5$x === undefined ? 0 : _ref5$x, _ref5$y = _ref5.y, y = _ref5$y === undefined ? 0 : _ref5$y; // TODO: Once thoroughly tested, and converted to using offsetX props, we could remove this one. if (!this.props.appendToParent) { x += window.scrollX || 0; y += window.scrollY || 0; } return { left: originalPosition.left + x, top: originalPosition.top + y }; } }, { key: '_onTooltipContentEnter', value: function _onTooltipContentEnter() { this.show(); } }, { key: '_onTooltipContentLeave', value: function _onTooltipContentLeave() { this._onMouseLeave(); } }, { key: 'isShown', value: function isShown() { return this.state.visible; } }]); return Tooltip; }(_BoldrComponent3.default); Tooltip.propTypes = { textAlign: _propTypes2.default.string, children: _propTypes2.default.node, content: _propTypes2.default.node.isRequired, placement: _propTypes2.default.oneOf(['top', 'right', 'bottom', 'left']), alignment: _propTypes2.default.oneOf(['top', 'right', 'bottom', 'left', 'center']), theme: _propTypes2.default.oneOf(['light', 'dark', 'error']), showDelay: _propTypes2.default.number, hideDelay: _propTypes2.default.number, showTrigger: _propTypes2.default.oneOf(['custom', 'mouseenter', 'mouseleave', 'click', 'focus', 'blur']), hideTrigger: _propTypes2.default.oneOf(['custom', 'mouseenter', 'mouseleave', 'click', 'focus', 'blur']), active: _propTypes2.default.bool, bounce: _propTypes2.default.bool, disabled: _propTypes2.default.bool, maxWidth: _propTypes2.default.string, onClickOutside: _propTypes2.default.func, /** * Callback to be called when the tooltip has been shown */ onShow: _propTypes2.default.func, zIndex: _propTypes2.default.number, /** * By default tooltip is appended to a body, to avoid CSS collisions. * But if you want your tooltip to scroll with a content, append tooltip to a parent. * Just make sure the CSS are not leaked. */ appendToParent: _propTypes2.default.bool, /** * Allows to shift the tooltip position by x and y pixels. * Both positive and negative values are accepted. */ moveBy: _propTypes2.default.shape({ x: _propTypes2.default.number, y: _propTypes2.default.number }), /** * Allows to position the arrow relative to tooltip. * Positive value calculates position from left/top. * Negative one calculates position from right/bottom. */ moveArrowTo: _propTypes2.default.number, size: _propTypes2.default.oneOf(['normal', 'large']), shouldCloseOnClickOutside: _propTypes2.default.bool }; Tooltip.defaultProps = { placement: 'left', alignment: 'center', showTrigger: 'mouseenter', hideTrigger: 'mouseleave', showDelay: 200, hideDelay: 500, zIndex: 2000, maxWidth: '1200px', onClickOutside: null, onShow: null, active: false, theme: 'light', disabled: false, children: null, size: 'normal', shouldCloseOnClickOutside: false, textAlign: 'center' }; var _initialiseProps = function _initialiseProps() { var _this6 = this; this._childNode = null; this._mountNode = null; this._showTimeout = null; this._hideTimeout = null; this._unmounted = false; this.state = { visible: false, hidden: true }; this._chainCallbacks = function (first, second) { return function (args) { if (first) { first.apply(_this6, args); } if (second) { second.apply(_this6, args); } }; }; }; exports.default = Tooltip;