boldr-ui
Version:
UI components for Boldr
506 lines (445 loc) • 16.3 kB
JavaScript
'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;