594 lines
20 KiB
JavaScript
594 lines
20 KiB
JavaScript
'use strict';
|
|
|
|
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
|
|
|
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 _class, _class2, _temp;
|
|
|
|
/* Decoraters */
|
|
|
|
|
|
/* Utils */
|
|
|
|
|
|
/* CSS */
|
|
|
|
|
|
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 _classnames = require('classnames');
|
|
|
|
var _classnames2 = _interopRequireDefault(_classnames);
|
|
|
|
var _staticMethods = require('./decorators/staticMethods');
|
|
|
|
var _staticMethods2 = _interopRequireDefault(_staticMethods);
|
|
|
|
var _windowListener = require('./decorators/windowListener');
|
|
|
|
var _windowListener2 = _interopRequireDefault(_windowListener);
|
|
|
|
var _customEvent = require('./decorators/customEvent');
|
|
|
|
var _customEvent2 = _interopRequireDefault(_customEvent);
|
|
|
|
var _isCapture = require('./decorators/isCapture');
|
|
|
|
var _isCapture2 = _interopRequireDefault(_isCapture);
|
|
|
|
var _getEffect = require('./decorators/getEffect');
|
|
|
|
var _getEffect2 = _interopRequireDefault(_getEffect);
|
|
|
|
var _trackRemoval = require('./decorators/trackRemoval');
|
|
|
|
var _trackRemoval2 = _interopRequireDefault(_trackRemoval);
|
|
|
|
var _getPosition = require('./utils/getPosition');
|
|
|
|
var _getPosition2 = _interopRequireDefault(_getPosition);
|
|
|
|
var _getTipContent = require('./utils/getTipContent');
|
|
|
|
var _getTipContent2 = _interopRequireDefault(_getTipContent);
|
|
|
|
var _aria = require('./utils/aria');
|
|
|
|
var _nodeListToArray = require('./utils/nodeListToArray');
|
|
|
|
var _nodeListToArray2 = _interopRequireDefault(_nodeListToArray);
|
|
|
|
var _style = require('./style');
|
|
|
|
var _style2 = _interopRequireDefault(_style);
|
|
|
|
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 ReactTooltip = (0, _staticMethods2.default)(_class = (0, _windowListener2.default)(_class = (0, _customEvent2.default)(_class = (0, _isCapture2.default)(_class = (0, _getEffect2.default)(_class = (0, _trackRemoval2.default)(_class = (_temp = _class2 = function (_Component) {
|
|
_inherits(ReactTooltip, _Component);
|
|
|
|
function ReactTooltip(props) {
|
|
_classCallCheck(this, ReactTooltip);
|
|
|
|
var _this = _possibleConstructorReturn(this, (ReactTooltip.__proto__ || Object.getPrototypeOf(ReactTooltip)).call(this, props));
|
|
|
|
_this.state = {
|
|
place: 'top', // Direction of tooltip
|
|
type: 'dark', // Color theme of tooltip
|
|
effect: 'float', // float or fixed
|
|
show: false,
|
|
border: false,
|
|
placeholder: '',
|
|
offset: {},
|
|
extraClass: '',
|
|
html: false,
|
|
delayHide: 0,
|
|
delayShow: 0,
|
|
event: props.event || null,
|
|
eventOff: props.eventOff || null,
|
|
currentEvent: null, // Current mouse event
|
|
currentTarget: null, // Current target of mouse event
|
|
ariaProps: (0, _aria.parseAria)(props), // aria- and role attributes
|
|
isEmptyTip: false,
|
|
disable: false
|
|
};
|
|
|
|
_this.bind(['showTooltip', 'updateTooltip', 'hideTooltip', 'globalRebuild', 'globalShow', 'globalHide', 'onWindowResize']);
|
|
|
|
_this.mount = true;
|
|
_this.delayShowLoop = null;
|
|
_this.delayHideLoop = null;
|
|
_this.intervalUpdateContent = null;
|
|
return _this;
|
|
}
|
|
|
|
/**
|
|
* For unify the bind and unbind listener
|
|
*/
|
|
|
|
|
|
_createClass(ReactTooltip, [{
|
|
key: 'bind',
|
|
value: function bind(methodArray) {
|
|
var _this2 = this;
|
|
|
|
methodArray.forEach(function (method) {
|
|
_this2[method] = _this2[method].bind(_this2);
|
|
});
|
|
}
|
|
}, {
|
|
key: 'componentDidMount',
|
|
value: function componentDidMount() {
|
|
var _props = this.props,
|
|
insecure = _props.insecure,
|
|
resizeHide = _props.resizeHide;
|
|
|
|
if (insecure) {
|
|
this.setStyleHeader(); // Set the style to the <link>
|
|
}
|
|
this.bindListener(); // Bind listener for tooltip
|
|
this.bindWindowEvents(resizeHide); // Bind global event for static method
|
|
}
|
|
}, {
|
|
key: 'componentWillReceiveProps',
|
|
value: function componentWillReceiveProps(props) {
|
|
var ariaProps = this.state.ariaProps;
|
|
|
|
var newAriaProps = (0, _aria.parseAria)(props);
|
|
|
|
var isChanged = Object.keys(newAriaProps).some(function (props) {
|
|
return newAriaProps[props] !== ariaProps[props];
|
|
});
|
|
if (isChanged) {
|
|
this.setState({ ariaProps: newAriaProps });
|
|
}
|
|
}
|
|
}, {
|
|
key: 'componentWillUnmount',
|
|
value: function componentWillUnmount() {
|
|
this.mount = false;
|
|
|
|
this.clearTimer();
|
|
|
|
this.unbindListener();
|
|
this.removeScrollListener();
|
|
this.unbindWindowEvents();
|
|
}
|
|
|
|
/**
|
|
* Pick out corresponded target elements
|
|
*/
|
|
|
|
}, {
|
|
key: 'getTargetArray',
|
|
value: function getTargetArray(id) {
|
|
var targetArray = void 0;
|
|
if (!id) {
|
|
targetArray = document.querySelectorAll('[data-tip]:not([data-for])');
|
|
} else {
|
|
var escaped = id.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
targetArray = document.querySelectorAll('[data-tip][data-for="' + escaped + '"]');
|
|
}
|
|
// targetArray is a NodeList, convert it to a real array
|
|
return (0, _nodeListToArray2.default)(targetArray);
|
|
}
|
|
|
|
/**
|
|
* Bind listener to the target elements
|
|
* These listeners used to trigger showing or hiding the tooltip
|
|
*/
|
|
|
|
}, {
|
|
key: 'bindListener',
|
|
value: function bindListener() {
|
|
var _this3 = this;
|
|
|
|
var _props2 = this.props,
|
|
id = _props2.id,
|
|
globalEventOff = _props2.globalEventOff;
|
|
|
|
var targetArray = this.getTargetArray(id);
|
|
|
|
targetArray.forEach(function (target) {
|
|
var isCaptureMode = _this3.isCapture(target);
|
|
var effect = _this3.getEffect(target);
|
|
if (target.getAttribute('currentItem') === null) {
|
|
target.setAttribute('currentItem', 'false');
|
|
}
|
|
_this3.unbindBasicListener(target);
|
|
|
|
if (_this3.isCustomEvent(target)) {
|
|
_this3.customBindListener(target);
|
|
return;
|
|
}
|
|
|
|
target.addEventListener('mouseenter', _this3.showTooltip, isCaptureMode);
|
|
if (effect === 'float') {
|
|
target.addEventListener('mousemove', _this3.updateTooltip, isCaptureMode);
|
|
}
|
|
target.addEventListener('mouseleave', _this3.hideTooltip, isCaptureMode);
|
|
});
|
|
|
|
// Global event to hide tooltip
|
|
if (globalEventOff) {
|
|
window.removeEventListener(globalEventOff, this.hideTooltip);
|
|
window.addEventListener(globalEventOff, this.hideTooltip, false);
|
|
}
|
|
|
|
// Track removal of targetArray elements from DOM
|
|
this.bindRemovalTracker();
|
|
}
|
|
|
|
/**
|
|
* Unbind listeners on target elements
|
|
*/
|
|
|
|
}, {
|
|
key: 'unbindListener',
|
|
value: function unbindListener() {
|
|
var _this4 = this;
|
|
|
|
var _props3 = this.props,
|
|
id = _props3.id,
|
|
globalEventOff = _props3.globalEventOff;
|
|
|
|
var targetArray = this.getTargetArray(id);
|
|
targetArray.forEach(function (target) {
|
|
_this4.unbindBasicListener(target);
|
|
if (_this4.isCustomEvent(target)) _this4.customUnbindListener(target);
|
|
});
|
|
|
|
if (globalEventOff) window.removeEventListener(globalEventOff, this.hideTooltip);
|
|
this.unbindRemovalTracker();
|
|
}
|
|
|
|
/**
|
|
* Invoke this before bind listener and ummount the compont
|
|
* it is necessary to invloke this even when binding custom event
|
|
* so that the tooltip can switch between custom and default listener
|
|
*/
|
|
|
|
}, {
|
|
key: 'unbindBasicListener',
|
|
value: function unbindBasicListener(target) {
|
|
var isCaptureMode = this.isCapture(target);
|
|
target.removeEventListener('mouseenter', this.showTooltip, isCaptureMode);
|
|
target.removeEventListener('mousemove', this.updateTooltip, isCaptureMode);
|
|
target.removeEventListener('mouseleave', this.hideTooltip, isCaptureMode);
|
|
}
|
|
|
|
/**
|
|
* When mouse enter, show the tooltip
|
|
*/
|
|
|
|
}, {
|
|
key: 'showTooltip',
|
|
value: function showTooltip(e, isGlobalCall) {
|
|
var _this5 = this;
|
|
|
|
if (isGlobalCall) {
|
|
// Don't trigger other elements belongs to other ReactTooltip
|
|
var targetArray = this.getTargetArray(this.props.id);
|
|
var isMyElement = targetArray.some(function (ele) {
|
|
return ele === e.currentTarget;
|
|
});
|
|
if (!isMyElement || this.state.show) return;
|
|
}
|
|
// Get the tooltip content
|
|
// calculate in this phrase so that tip width height can be detected
|
|
var _props4 = this.props,
|
|
children = _props4.children,
|
|
multiline = _props4.multiline,
|
|
getContent = _props4.getContent;
|
|
|
|
var originTooltip = e.currentTarget.getAttribute('data-tip');
|
|
var isMultiline = e.currentTarget.getAttribute('data-multiline') || multiline || false;
|
|
|
|
// Generate tootlip content
|
|
var content = void 0;
|
|
if (getContent) {
|
|
if (Array.isArray(getContent)) {
|
|
content = getContent[0] && getContent[0]();
|
|
} else {
|
|
content = getContent();
|
|
}
|
|
}
|
|
var placeholder = (0, _getTipContent2.default)(originTooltip, children, content, isMultiline);
|
|
var isEmptyTip = typeof placeholder === 'string' && placeholder === '' || placeholder === null;
|
|
|
|
// If it is focus event or called by ReactTooltip.show, switch to `solid` effect
|
|
var switchToSolid = e instanceof window.FocusEvent || isGlobalCall;
|
|
|
|
// if it needs to skip adding hide listener to scroll
|
|
var scrollHide = true;
|
|
if (e.currentTarget.getAttribute('data-scroll-hide')) {
|
|
scrollHide = e.currentTarget.getAttribute('data-scroll-hide') === 'true';
|
|
} else if (this.props.scrollHide != null) {
|
|
scrollHide = this.props.scrollHide;
|
|
}
|
|
|
|
// To prevent previously created timers from triggering
|
|
this.clearTimer();
|
|
|
|
this.setState({
|
|
placeholder: placeholder,
|
|
isEmptyTip: isEmptyTip,
|
|
place: e.currentTarget.getAttribute('data-place') || this.props.place || 'top',
|
|
type: e.currentTarget.getAttribute('data-type') || this.props.type || 'dark',
|
|
effect: switchToSolid && 'solid' || this.getEffect(e.currentTarget),
|
|
offset: e.currentTarget.getAttribute('data-offset') || this.props.offset || {},
|
|
html: e.currentTarget.getAttribute('data-html') ? e.currentTarget.getAttribute('data-html') === 'true' : this.props.html || false,
|
|
delayShow: e.currentTarget.getAttribute('data-delay-show') || this.props.delayShow || 0,
|
|
delayHide: e.currentTarget.getAttribute('data-delay-hide') || this.props.delayHide || 0,
|
|
border: e.currentTarget.getAttribute('data-border') ? e.currentTarget.getAttribute('data-border') === 'true' : this.props.border || false,
|
|
extraClass: e.currentTarget.getAttribute('data-class') || this.props.class || this.props.className || '',
|
|
disable: e.currentTarget.getAttribute('data-tip-disable') ? e.currentTarget.getAttribute('data-tip-disable') === 'true' : this.props.disable || false
|
|
}, function () {
|
|
if (scrollHide) _this5.addScrollListener(e);
|
|
_this5.updateTooltip(e);
|
|
|
|
if (getContent && Array.isArray(getContent)) {
|
|
_this5.intervalUpdateContent = setInterval(function () {
|
|
if (_this5.mount) {
|
|
var _getContent = _this5.props.getContent;
|
|
|
|
var _placeholder = (0, _getTipContent2.default)(originTooltip, _getContent[0](), isMultiline);
|
|
var _isEmptyTip = typeof _placeholder === 'string' && _placeholder === '';
|
|
_this5.setState({
|
|
placeholder: _placeholder,
|
|
isEmptyTip: _isEmptyTip
|
|
});
|
|
}
|
|
}, getContent[1]);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* When mouse hover, updatetooltip
|
|
*/
|
|
|
|
}, {
|
|
key: 'updateTooltip',
|
|
value: function updateTooltip(e) {
|
|
var _this6 = this;
|
|
|
|
var _state = this.state,
|
|
delayShow = _state.delayShow,
|
|
show = _state.show,
|
|
isEmptyTip = _state.isEmptyTip,
|
|
disable = _state.disable;
|
|
var afterShow = this.props.afterShow;
|
|
var placeholder = this.state.placeholder;
|
|
|
|
var delayTime = show ? 0 : parseInt(delayShow, 10);
|
|
var eventTarget = e.currentTarget;
|
|
|
|
if (isEmptyTip || disable) return; // if the tooltip is empty, disable the tooltip
|
|
var updateState = function updateState() {
|
|
if (Array.isArray(placeholder) && placeholder.length > 0 || placeholder) {
|
|
var isInvisible = !_this6.state.show;
|
|
_this6.setState({
|
|
currentEvent: e,
|
|
currentTarget: eventTarget,
|
|
show: true
|
|
}, function () {
|
|
_this6.updatePosition();
|
|
if (isInvisible && afterShow) afterShow();
|
|
});
|
|
}
|
|
};
|
|
|
|
clearTimeout(this.delayShowLoop);
|
|
if (delayShow) {
|
|
this.delayShowLoop = setTimeout(updateState, delayTime);
|
|
} else {
|
|
updateState();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When mouse leave, hide tooltip
|
|
*/
|
|
|
|
}, {
|
|
key: 'hideTooltip',
|
|
value: function hideTooltip(e, hasTarget) {
|
|
var _this7 = this;
|
|
|
|
var _state2 = this.state,
|
|
delayHide = _state2.delayHide,
|
|
isEmptyTip = _state2.isEmptyTip,
|
|
disable = _state2.disable;
|
|
var afterHide = this.props.afterHide;
|
|
|
|
if (!this.mount) return;
|
|
if (isEmptyTip || disable) return; // if the tooltip is empty, disable the tooltip
|
|
if (hasTarget) {
|
|
// Don't trigger other elements belongs to other ReactTooltip
|
|
var targetArray = this.getTargetArray(this.props.id);
|
|
var isMyElement = targetArray.some(function (ele) {
|
|
return ele === e.currentTarget;
|
|
});
|
|
if (!isMyElement || !this.state.show) return;
|
|
}
|
|
var resetState = function resetState() {
|
|
var isVisible = _this7.state.show;
|
|
_this7.setState({
|
|
show: false
|
|
}, function () {
|
|
_this7.removeScrollListener();
|
|
if (isVisible && afterHide) afterHide();
|
|
});
|
|
};
|
|
|
|
this.clearTimer();
|
|
if (delayHide) {
|
|
this.delayHideLoop = setTimeout(resetState, parseInt(delayHide, 10));
|
|
} else {
|
|
resetState();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add scroll eventlistener when tooltip show
|
|
* automatically hide the tooltip when scrolling
|
|
*/
|
|
|
|
}, {
|
|
key: 'addScrollListener',
|
|
value: function addScrollListener(e) {
|
|
var isCaptureMode = this.isCapture(e.currentTarget);
|
|
window.addEventListener('scroll', this.hideTooltip, isCaptureMode);
|
|
}
|
|
}, {
|
|
key: 'removeScrollListener',
|
|
value: function removeScrollListener() {
|
|
window.removeEventListener('scroll', this.hideTooltip);
|
|
}
|
|
|
|
// Calculation the position
|
|
|
|
}, {
|
|
key: 'updatePosition',
|
|
value: function updatePosition() {
|
|
var _this8 = this;
|
|
|
|
var _state3 = this.state,
|
|
currentEvent = _state3.currentEvent,
|
|
currentTarget = _state3.currentTarget,
|
|
place = _state3.place,
|
|
effect = _state3.effect,
|
|
offset = _state3.offset;
|
|
|
|
var node = _reactDom2.default.findDOMNode(this);
|
|
var result = (0, _getPosition2.default)(currentEvent, currentTarget, node, place, effect, offset);
|
|
|
|
if (result.isNewState) {
|
|
// Switch to reverse placement
|
|
return this.setState(result.newState, function () {
|
|
_this8.updatePosition();
|
|
});
|
|
}
|
|
// Set tooltip position
|
|
node.style.left = result.position.left + 'px';
|
|
node.style.top = result.position.top + 'px';
|
|
}
|
|
|
|
/**
|
|
* Set style tag in header
|
|
* in this way we can insert default css
|
|
*/
|
|
|
|
}, {
|
|
key: 'setStyleHeader',
|
|
value: function setStyleHeader() {
|
|
if (!document.getElementsByTagName('head')[0].querySelector('style[id="react-tooltip"]')) {
|
|
var tag = document.createElement('style');
|
|
tag.id = 'react-tooltip';
|
|
tag.innerHTML = _style2.default;
|
|
document.getElementsByTagName('head')[0].appendChild(tag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* CLear all kinds of timeout of interval
|
|
*/
|
|
|
|
}, {
|
|
key: 'clearTimer',
|
|
value: function clearTimer() {
|
|
clearTimeout(this.delayShowLoop);
|
|
clearTimeout(this.delayHideLoop);
|
|
clearInterval(this.intervalUpdateContent);
|
|
}
|
|
}, {
|
|
key: 'render',
|
|
value: function render() {
|
|
var _state4 = this.state,
|
|
placeholder = _state4.placeholder,
|
|
extraClass = _state4.extraClass,
|
|
html = _state4.html,
|
|
ariaProps = _state4.ariaProps,
|
|
disable = _state4.disable,
|
|
isEmptyTip = _state4.isEmptyTip;
|
|
|
|
var tooltipClass = (0, _classnames2.default)('__react_component_tooltip', { 'show': this.state.show && !disable && !isEmptyTip }, { 'border': this.state.border }, { 'place-top': this.state.place === 'top' }, { 'place-bottom': this.state.place === 'bottom' }, { 'place-left': this.state.place === 'left' }, { 'place-right': this.state.place === 'right' }, { 'type-dark': this.state.type === 'dark' }, { 'type-success': this.state.type === 'success' }, { 'type-warning': this.state.type === 'warning' }, { 'type-error': this.state.type === 'error' }, { 'type-info': this.state.type === 'info' }, { 'type-light': this.state.type === 'light' });
|
|
|
|
var Wrapper = this.props.wrapper;
|
|
if (ReactTooltip.supportedWrappers.indexOf(Wrapper) < 0) {
|
|
Wrapper = ReactTooltip.defaultProps.wrapper;
|
|
}
|
|
|
|
if (html) {
|
|
return _react2.default.createElement(Wrapper, _extends({ className: tooltipClass + ' ' + extraClass
|
|
}, ariaProps, {
|
|
'data-id': 'tooltip',
|
|
dangerouslySetInnerHTML: { __html: placeholder } }));
|
|
} else {
|
|
return _react2.default.createElement(
|
|
Wrapper,
|
|
_extends({ className: tooltipClass + ' ' + extraClass
|
|
}, ariaProps, {
|
|
'data-id': 'tooltip' }),
|
|
placeholder
|
|
);
|
|
}
|
|
}
|
|
}]);
|
|
|
|
return ReactTooltip;
|
|
}(_react.Component), _class2.propTypes = {
|
|
children: _propTypes2.default.any,
|
|
place: _propTypes2.default.string,
|
|
type: _propTypes2.default.string,
|
|
effect: _propTypes2.default.string,
|
|
offset: _propTypes2.default.object,
|
|
multiline: _propTypes2.default.bool,
|
|
border: _propTypes2.default.bool,
|
|
insecure: _propTypes2.default.bool,
|
|
class: _propTypes2.default.string,
|
|
className: _propTypes2.default.string,
|
|
id: _propTypes2.default.string,
|
|
html: _propTypes2.default.bool,
|
|
delayHide: _propTypes2.default.number,
|
|
delayShow: _propTypes2.default.number,
|
|
event: _propTypes2.default.string,
|
|
eventOff: _propTypes2.default.string,
|
|
watchWindow: _propTypes2.default.bool,
|
|
isCapture: _propTypes2.default.bool,
|
|
globalEventOff: _propTypes2.default.string,
|
|
getContent: _propTypes2.default.any,
|
|
afterShow: _propTypes2.default.func,
|
|
afterHide: _propTypes2.default.func,
|
|
disable: _propTypes2.default.bool,
|
|
scrollHide: _propTypes2.default.bool,
|
|
resizeHide: _propTypes2.default.bool,
|
|
wrapper: _propTypes2.default.string
|
|
}, _class2.defaultProps = {
|
|
insecure: true,
|
|
resizeHide: true,
|
|
wrapper: 'div'
|
|
}, _class2.supportedWrappers = ['div', 'span'], _temp)) || _class) || _class) || _class) || _class) || _class) || _class;
|
|
|
|
/* export default not fit for standalone, it will exports {default:...} */
|
|
|
|
|
|
module.exports = ReactTooltip; |