UNPKG

wix-style-react

Version:
698 lines (609 loc) • 24.6 kB
'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;