wix-style-react
Version:
wix-style-react
698 lines (609 loc) • 24.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
var _class, _temp, _initialiseProps;
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _WixComponent2 = require('../BaseComponents/WixComponent');
var _WixComponent3 = _interopRequireDefault(_WixComponent2);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _TooltipContent = require('./TooltipContent');
var _TooltipContent2 = _interopRequireDefault(_TooltipContent);
var _TooltipPosition = require('./TooltipPosition');
var _TooltipPosition2 = _interopRequireDefault(_TooltipPosition);
var _TooltipContent3 = require('./TooltipContent.scss');
var _TooltipContent4 = _interopRequireDefault(_TooltipContent3);
var _TooltipContainerStrategy = require('./TooltipContainerStrategy');
var _throttle = require('lodash/throttle');
var _throttle2 = _interopRequireDefault(_throttle);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var renderSubtreeIntoContainer = _reactDom2.default.unstable_renderSubtreeIntoContainer;
// TestId is a uniq Tooltip id, used to find the content element in tests.
var testId = 0;
function nextTestId() {
testId++;
return testId;
}
//maintain a 60fps rendering
var createAThrottledOptimizedFunction = function createAThrottledOptimizedFunction(cb) {
return function () {
return window.requestAnimationFrame((0, _throttle2.default)(cb, 16));
};
};
var popoverConfig = {
contentClassName: _TooltipContent4.default.popoverTooltipContent,
theme: 'light',
showTrigger: 'click',
hideTrigger: 'click'
};
/** A Tooltip component */
var Tooltip = (_temp = _class = function (_WixComponent) {
_inherits(Tooltip, _WixComponent);
function Tooltip(props) {
_classCallCheck(this, Tooltip);
var _this = _possibleConstructorReturn(this, (Tooltip.__proto__ || Object.getPrototypeOf(Tooltip)).call(this, props));
_initialiseProps.call(_this);
_this.state = {
visible: false,
hidden: true
};
_this._tooltipContainerStrategy = new _TooltipContainerStrategy.TooltipContainerStrategy(props.appendTo, props.appendToParent, props.appendByPredicate);
_this.testId = nextTestId();
_this.contentHook = _this._createContentHook();
return _this;
}
_createClass(Tooltip, [{
key: '_setContentDataHook',
value: function _setContentDataHook() {
if (this._childNode) {
this._childNode.setAttribute('data-content-hook', this.contentHook);
}
}
}, {
key: 'componentElements',
value: function componentElements() {
var elements = _get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(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();
}
this.props.onClickOutside && this.props.onClickOutside(e);
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps) {
_get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(Tooltip.prototype), 'componentDidUpdate', this).call(this, prevProps);
if (prevProps.dataHook !== this.props.dataHook) {
this.contentHook = this._createContentHook();
this._setContentDataHook();
}
this.renderTooltipIntoContainer();
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
_get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(Tooltip.prototype), 'componentDidMount', this) && _get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(Tooltip.prototype), 'componentDidMount', this).call(this);
this._setContentDataHook();
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
_get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(Tooltip.prototype), 'componentWillUnmount', this) && _get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(Tooltip.prototype), 'componentWillUnmount', this).call(this);
this._unmounted = true;
this._removeNode();
this._getContainer() && this.hide();
if (this._showInterval) {
clearInterval(this._showInterval);
}
}
}, {
key: 'componentWillMount',
value: function componentWillMount() {
_get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(Tooltip.prototype), 'componentWillMount', this) && _get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(Tooltip.prototype), 'componentWillMount', this).call(this);
if (this.props.active) {
this.show();
}
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
_get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(Tooltip.prototype), 'componentWillReceiveProps', this) && _get(Tooltip.prototype.__proto__ || Object.getPrototypeOf(Tooltip.prototype), 'componentWillReceiveProps', this).call(this, nextProps);
if (nextProps.active !== this.props.active || nextProps.disabled !== this.props.disabled) {
if (this.state.visible && this.getTriggers().hideTrigger === 'custom') {
if (!nextProps.active || nextProps.disabled) {
this.hide(nextProps);
}
}
if (!this.state.visible && this.getTriggers().showTrigger === 'custom') {
if (nextProps.active && !nextProps.disabled) {
this.show(nextProps);
}
}
}
}
}, {
key: 'getTriggers',
value: function getTriggers() {
return {
hideTrigger: this.props.popover ? 'click' : this.props.hideTrigger,
showTrigger: this.props.popover ? 'click' : this.props.showTrigger
};
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
var child = Array.isArray(this.props.children) ? this.props.children[0] : this.props.children;
if (child) {
return (0, _react.cloneElement)(child, {
ref: function ref(_ref) {
return _this2._childNode = _reactDom2.default.findDOMNode(_ref);
},
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 _react2.default.createElement('div', null);
}
}
}, {
key: '_getContainer',
value: function _getContainer() {
return this._tooltipContainerStrategy.getContainer(this._childNode);
}
}, {
key: '_doShow',
value: function _doShow() {
var _this3 = this;
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props;
if (typeof document === 'undefined') {
return;
}
if (props.onShow) {
props.onShow();
}
this.setState({ visible: true }, function () {
if (!_this3._mountNode) {
_this3._mountNode = document.createElement('div');
var container = _this3._getContainer();
if (container) {
container.appendChild(_this3._mountNode);
_this3._containerScrollHandler = createAThrottledOptimizedFunction(function () {
return _this3._updatePosition(_this3.tooltipContent);
});
container.addEventListener('scroll', _this3._containerScrollHandler);
}
}
_this3._showTimeout = null;
_this3.renderTooltipIntoContainer();
// To prevent any possible jumping of tooltip, we need to try to update tooltip position in sync way
var tooltipNode = _reactDom2.default.findDOMNode(_this3.tooltipContent);
if (tooltipNode) {
_this3._updatePosition(_this3.tooltipContent);
}
var fw = 0;
var sw = 0;
// we need to set tooltip position after render of tooltip into container, on next event loop
setTimeout(function () {
var iterations = 0;
var pixelChange = 0;
do {
var _tooltipNode = _reactDom2.default.findDOMNode(_this3.tooltipContent);
if (_tooltipNode) {
fw = _this3._getRect(_tooltipNode).width;
_this3._updatePosition(_this3.tooltipContent);
sw = _this3._getRect(_tooltipNode).width;
}
++iterations;
pixelChange = Math.abs(fw - sw);
} while (!props.appendToParent && pixelChange > 0.1 && iterations < 10);
});
});
}
}, {
key: '_removeNode',
value: function _removeNode() {
if (this._mountNode) {
_reactDom2.default.unmountComponentAtNode(this._mountNode);
var container = this._getContainer();
if (container) {
container.removeChild(this._mountNode);
container.removeEventListener('scroll', this._containerScrollHandler);
}
this._mountNode = null;
}
}
}, {
key: '_hideOrShow',
value: function _hideOrShow(event) {
if (this.getTriggers().hideTrigger === event && !this.state.hidden) {
this.hide();
} else if (this.getTriggers().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 = style.left + 'px';
}
var arrowStyles = this._adjustArrowPosition(this.props.placement, this.props.moveArrowTo);
if (Object.keys(arrowStyles).length) {
var arrow = tooltipNode.querySelector('.' + _TooltipContent4.default.arrow);
arrow && Object.keys(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
};
}
var container = this._getContainer(el);
if (container !== document.body) {
var containerRect = container.getBoundingClientRect();
var selfRect = el.getBoundingClientRect();
return {
left: selfRect.left - containerRect.left + container.scrollLeft,
top: selfRect.top - containerRect.top + container.scrollTop,
width: selfRect.width,
height: selfRect.height
};
}
return el.getBoundingClientRect();
}
}, {
key: '_adjustPosition',
value: function _adjustPosition(originalPosition) {
var _ref2 = this.props.moveBy || {},
_ref2$x = _ref2.x,
x = _ref2$x === undefined ? 0 : _ref2$x,
_ref2$y = _ref2.y,
y = _ref2$y === undefined ? 0 : _ref2$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() {
if (this.getTriggers().showTrigger === 'custom') {
return;
}
this.show();
}
}, {
key: '_onTooltipContentLeave',
value: function _onTooltipContentLeave() {
if (this.getTriggers().hideTrigger === 'custom') {
return;
}
this._onMouseLeave();
}
}, {
key: 'isShown',
value: function isShown() {
return this.state.visible;
}
}]);
return Tooltip;
}(_WixComponent3.default), _class.displayName = 'Tooltip', _class.propTypes = {
dataHook: _propTypes2.default.string,
/** alignment of the tooltip's text */
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,
/** Apply popover styles and even triggers */
popover: _propTypes2.default.bool,
/** The tooltip max width */
maxWidth: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
/** The tooltip min width */
minWidth: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
/** Callback when cliking outside */
onClickOutside: _propTypes2.default.func,
/** override the theme text color of the tooltip */
color: _propTypes2.default.string,
/** override the theme text line height of the tooltip */
lineHeight: _propTypes2.default.string,
/** Callback to be called when the tooltip has been shown */
onShow: _propTypes2.default.func,
/** Callback to be called when the tooltip has been hidden */
onHide: _propTypes2.default.func,
/** z index of the tooltip */
zIndex: _propTypes2.default.number,
/**
* In some cases when you need a tooltip scroll with your element, you can append the tooltip to the direct parent, just
* don't forget to apply `relative`, `absolute` positioning. And be aware that some of your styles may leak into
* tooltip content.
*/
appendToParent: _propTypes2.default.bool,
/**
* In cases where you need to append the tooltip to some ancestor which is not the direct parent, you can pass a
* predicate function of the form `(element: DOMElement) => Boolean`, and the tooltip will be attached to the
* closest ancestor for which the predicate returns `true`
*/
appendByPredicate: _propTypes2.default.func,
/** Element to attach the tooltip to */
appendTo: _propTypes2.default.any,
/**
* 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,
relative: _propTypes2.default.bool,
/** Allows changing the padding of the content */
padding: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
/** Allows updating the tooltip position **/
shouldUpdatePosition: _propTypes2.default.bool,
/** Show Tooltip Immediately - with no delay and no animation */
showImmediately: _propTypes2.default.bool,
/** Show an arrow shape */
showArrow: _propTypes2.default.bool
}, _class.defaultProps = {
placement: 'top',
alignment: 'center',
showTrigger: 'mouseenter',
hideTrigger: 'mouseleave',
showDelay: 200,
hideDelay: 0,
zIndex: 2000,
maxWidth: '204px',
onClickOutside: null,
onShow: null,
onHide: null,
active: false,
theme: 'light',
disabled: false,
children: null,
size: 'normal',
shouldCloseOnClickOutside: false,
textAlign: 'left',
relative: false,
shouldUpdatePosition: false,
showImmediately: false,
showArrow: true
}, _initialiseProps = function _initialiseProps() {
var _this4 = this;
this._childNode = null;
this._mountNode = null;
this._showTimeout = null;
this._showInterval = null;
this._hideTimeout = null;
this._unmounted = false;
this._containerScrollHandler = null;
this._createContentHook = function () {
return 'tooltip-content-' + (_this4.props.dataHook || '') + '-' + _this4.testId;
};
this.renderTooltipIntoContainer = function () {
if (_this4._mountNode && _this4.state.visible) {
var contentClassName = _this4.props.popover ? popoverConfig.contentClassName : '';
var theme = _this4.props.popover ? popoverConfig.theme : _this4.props.theme;
var arrowPlacement = {
top: 'bottom',
left: 'right',
right: 'left',
bottom: 'top'
};
var _position = _this4.props.relative ? 'relative' : 'absolute';
var tooltip = _react2.default.createElement(
_TooltipContent2.default,
{
dataHook: _this4.contentHook,
contentClassName: contentClassName,
onMouseEnter: function onMouseEnter() {
return _this4._onTooltipContentEnter();
},
onMouseLeave: function onMouseLeave() {
return _this4._onTooltipContentLeave();
},
ref: function ref(_ref3) {
if (_this4.props.relative) {
_this4.tooltipContent = _ref3 && _ref3.tooltip;
} else {
_this4.tooltipContent = _ref3;
}
},
showImmediately: _this4.props.showImmediately,
theme: theme,
bounce: _this4.props.bounce,
arrowPlacement: arrowPlacement[_this4.props.placement],
style: { zIndex: _this4.props.zIndex, _position: _position },
arrowStyle: _this4.state.arrowStyle,
maxWidth: _this4.props.maxWidth,
padding: _this4.props.padding,
minWidth: _this4.props.minWidth,
size: _this4.props.size,
textAlign: _this4.props.textAlign,
lineHeight: _this4.props.lineHeight,
color: _this4.props.color,
showArrow: _this4.props.showArrow
},
_this4.props.content
);
renderSubtreeIntoContainer(_this4, tooltip, _this4._mountNode);
if (_this4.props.shouldUpdatePosition) {
setTimeout(function () {
_this4._updatePosition(_this4.tooltipContent);
});
}
}
};
this._chainCallbacks = function (first, second) {
return function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
if (first) {
first.apply(_this4, args);
}
if (second) {
second.apply(_this4, args);
}
};
};
this.show = function () {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this4.props;
if (props.disabled) {
return;
}
if (_this4._unmounted) {
return;
}
_this4.setState({ hidden: false });
if (_this4._hideTimeout) {
clearTimeout(_this4._hideTimeout);
_this4._hideTimeout = null;
}
if (_this4._showTimeout) {
return;
}
if (!_this4.state.visible) {
if (_this4.props.showImmediately) {
_this4._doShow(props);
} else {
_this4._showTimeout = setTimeout(function () {
return _this4._doShow(props);
}, props.showDelay);
}
}
};
this.hide = function () {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this4.props;
_this4.setState({ hidden: true });
if (_this4._showTimeout) {
clearTimeout(_this4._showTimeout);
_this4._showTimeout = null;
}
if (_this4._hideTimeout) {
return;
}
if (_this4.state.visible) {
var hideLazy = function hideLazy() {
props.onHide && props.onHide();
_this4._hideTimeout = null;
if (!_this4._unmounted) {
_this4._removeNode();
_this4.setState({ visible: false });
}
};
if (_this4._unmounted) {
return hideLazy();
}
_this4._hideTimeout = setTimeout(hideLazy, props.hideDelay);
}
};
}, _temp);
exports.default = Tooltip;