@alifd/next
Version:
A configurable component library for web built on React.
396 lines (337 loc) • 13.3 kB
JavaScript
import _objectWithoutProperties from 'babel-runtime/helpers/objectWithoutProperties';
import _extends from 'babel-runtime/helpers/extends';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
import _inherits from 'babel-runtime/helpers/inherits';
var _class, _temp;
import React, { Component, Children } from 'react';
import { findDOMNode } from 'react-dom';
import { polyfill } from 'react-lifecycles-compat';
import PropTypes from 'prop-types';
import { func, KEYCODE } from '../util';
import Overlay from './overlay';
var noop = func.noop,
makeChain = func.makeChain,
bindCtx = func.bindCtx;
/**
* Overlay.Popup
* @description 继承 Overlay 的 API,除非特别说明
* */
var Popup = (_temp = _class = function (_Component) {
_inherits(Popup, _Component);
function Popup(props) {
_classCallCheck(this, Popup);
var _this = _possibleConstructorReturn(this, _Component.call(this, props));
_this.state = {
visible: typeof props.visible === 'undefined' ? props.defaultVisible : props.visible
};
bindCtx(_this, ['handleTriggerClick', 'handleTriggerKeyDown', 'handleTriggerMouseEnter', 'handleTriggerMouseLeave', 'handleTriggerFocus', 'handleTriggerBlur', 'handleContentMouseEnter', 'handleContentMouseLeave', 'handleContentMouseDown', 'handleRequestClose', 'handleMaskMouseEnter', 'handleMaskMouseLeave']);
return _this;
}
Popup.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) {
if ('visible' in nextProps) {
return _extends({}, prevState, {
visible: nextProps.visible
});
}
return null;
};
Popup.prototype.componentWillUnmount = function componentWillUnmount() {
var _this2 = this;
['_timer', '_hideTimer', '_showTimer'].forEach(function (time) {
_this2[time] && clearTimeout(_this2[time]);
});
};
Popup.prototype.handleVisibleChange = function handleVisibleChange(visible, type, e) {
if (!('visible' in this.props)) {
this.setState({
visible: visible
});
}
this.props.onVisibleChange(visible, type, e);
};
Popup.prototype.handleTriggerClick = function handleTriggerClick(e) {
if (this.state.visible && !this.props.canCloseByTrigger) {
return;
}
this.handleVisibleChange(!this.state.visible, 'fromTrigger', e);
};
Popup.prototype.handleTriggerKeyDown = function handleTriggerKeyDown(e) {
var triggerClickKeycode = this.props.triggerClickKeycode;
var keycodes = Array.isArray(triggerClickKeycode) ? triggerClickKeycode : [triggerClickKeycode];
if (keycodes.includes(e.keyCode)) {
e.preventDefault();
this.handleTriggerClick(e);
}
};
Popup.prototype.handleTriggerMouseEnter = function handleTriggerMouseEnter(e) {
var _this3 = this;
this._mouseNotFirstOnMask = false;
if (this._hideTimer) {
clearTimeout(this._hideTimer);
this._hideTimer = null;
}
if (this._showTimer) {
clearTimeout(this._showTimer);
this._showTimer = null;
}
if (!this.state.visible) {
this._showTimer = setTimeout(function () {
_this3.handleVisibleChange(true, 'fromTrigger', e);
}, this.props.delay);
}
};
Popup.prototype.handleTriggerMouseLeave = function handleTriggerMouseLeave(e, type) {
var _this4 = this;
if (this._showTimer) {
clearTimeout(this._showTimer);
this._showTimer = null;
}
if (this.state.visible) {
this._hideTimer = setTimeout(function () {
_this4.handleVisibleChange(false, type || 'fromTrigger', e);
}, this.props.delay);
}
};
Popup.prototype.handleTriggerFocus = function handleTriggerFocus(e) {
this.handleVisibleChange(true, 'fromTrigger', e);
};
Popup.prototype.handleTriggerBlur = function handleTriggerBlur(e) {
if (!this._isForwardContent) {
this.handleVisibleChange(false, 'fromTrigger', e);
}
this._isForwardContent = false;
};
Popup.prototype.handleContentMouseDown = function handleContentMouseDown() {
this._isForwardContent = true;
};
Popup.prototype.handleContentMouseEnter = function handleContentMouseEnter() {
clearTimeout(this._hideTimer);
};
Popup.prototype.handleContentMouseLeave = function handleContentMouseLeave(e) {
this.handleTriggerMouseLeave(e, 'fromContent');
};
Popup.prototype.handleMaskMouseEnter = function handleMaskMouseEnter() {
if (!this._mouseNotFirstOnMask) {
clearTimeout(this._hideTimer);
this._hideTimer = null;
this._mouseNotFirstOnMask = false;
}
};
Popup.prototype.handleMaskMouseLeave = function handleMaskMouseLeave() {
this._mouseNotFirstOnMask = true;
};
Popup.prototype.handleRequestClose = function handleRequestClose(type, e) {
this.handleVisibleChange(false, type, e);
};
Popup.prototype.renderTrigger = function renderTrigger() {
var _this5 = this;
var _props = this.props,
trigger = _props.trigger,
disabled = _props.disabled;
var props = {
key: 'trigger',
'aria-haspopup': true,
'aria-expanded': this.state.visible
};
if (!this.state.visible) {
props['aria-describedby'] = undefined;
}
if (!disabled) {
var triggerType = this.props.triggerType;
var triggerTypes = Array.isArray(triggerType) ? triggerType : [triggerType];
var _ref = trigger && trigger.props || {},
onClick = _ref.onClick,
onKeyDown = _ref.onKeyDown,
onMouseEnter = _ref.onMouseEnter,
onMouseLeave = _ref.onMouseLeave,
onFocus = _ref.onFocus,
onBlur = _ref.onBlur;
triggerTypes.forEach(function (triggerType) {
switch (triggerType) {
case 'click':
props.onClick = makeChain(_this5.handleTriggerClick, onClick);
props.onKeyDown = makeChain(_this5.handleTriggerKeyDown, onKeyDown);
break;
case 'hover':
props.onMouseEnter = makeChain(_this5.handleTriggerMouseEnter, onMouseEnter);
props.onMouseLeave = makeChain(_this5.handleTriggerMouseLeave, onMouseLeave);
break;
case 'focus':
props.onFocus = makeChain(_this5.handleTriggerFocus, onFocus);
props.onBlur = makeChain(_this5.handleTriggerBlur, onBlur);
break;
default:
break;
}
});
}
return trigger && React.cloneElement(trigger, props);
};
Popup.prototype.renderContent = function renderContent() {
var _this6 = this;
var _props2 = this.props,
children = _props2.children,
triggerType = _props2.triggerType;
var triggerTypes = Array.isArray(triggerType) ? triggerType : [triggerType];
var content = Children.only(children);
var _content$props = content.props,
onMouseDown = _content$props.onMouseDown,
onMouseEnter = _content$props.onMouseEnter,
onMouseLeave = _content$props.onMouseLeave;
var props = {
key: 'portal'
};
triggerTypes.forEach(function (triggerType) {
switch (triggerType) {
case 'focus':
props.onMouseDown = makeChain(_this6.handleContentMouseDown, onMouseDown);
break;
case 'hover':
props.onMouseEnter = makeChain(_this6.handleContentMouseEnter, onMouseEnter);
props.onMouseLeave = makeChain(_this6.handleContentMouseLeave, onMouseLeave);
break;
default:
break;
}
});
return React.cloneElement(content, props);
};
Popup.prototype.renderPortal = function renderPortal() {
var _this7 = this;
var _props3 = this.props,
target = _props3.target,
safeNode = _props3.safeNode,
followTrigger = _props3.followTrigger,
triggerType = _props3.triggerType,
hasMask = _props3.hasMask,
wrapperStyle = _props3.wrapperStyle,
others = _objectWithoutProperties(_props3, ['target', 'safeNode', 'followTrigger', 'triggerType', 'hasMask', 'wrapperStyle']);
var container = this.props.container;
var findTriggerNode = function findTriggerNode() {
return findDOMNode(_this7);
};
var safeNodes = Array.isArray(safeNode) ? [].concat(safeNode) : [safeNode];
safeNodes.unshift(findTriggerNode);
var newWrapperStyle = wrapperStyle || {};
if (followTrigger) {
container = function container(trigger) {
return trigger && trigger.parentNode || trigger;
};
newWrapperStyle.position = 'relative';
}
if (triggerType === 'hover' && hasMask) {
others.onMaskMouseEnter = this.handleMaskMouseEnter;
others.onMaskMouseLeave = this.handleMaskMouseLeave;
}
return React.createElement(
Overlay,
_extends({}, others, {
key: 'overlay',
ref: function ref(overlay) {
return _this7.overlay = overlay;
},
visible: this.state.visible,
target: target || findTriggerNode,
container: container,
safeNode: safeNodes,
wrapperStyle: newWrapperStyle,
triggerType: triggerType,
hasMask: hasMask,
onRequestClose: this.handleRequestClose
}),
this.props.children && this.renderContent()
);
};
Popup.prototype.render = function render() {
return [this.renderTrigger(), this.renderPortal()];
};
return Popup;
}(Component), _class.propTypes = {
/**
* 弹层内容
*/
children: PropTypes.node,
/**
* 触发弹层显示或隐藏的元素
*/
trigger: PropTypes.element,
/**
* 触发弹层显示或隐藏的操作类型,可以是 'click','hover','focus',或者它们组成的数组,如 ['hover', 'focus']
*/
triggerType: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
/**
* 当 triggerType 为 click 时才生效,可自定义触发弹层显示的键盘码
*/
triggerClickKeycode: PropTypes.oneOfType([PropTypes.number, PropTypes.array]),
/**
* 弹层当前是否显示
*/
visible: PropTypes.bool,
/**
* 弹层默认是否显示
*/
defaultVisible: PropTypes.bool,
/**
* 弹层显示或隐藏时触发的回调函数
* @param {Boolean} visible 弹层是否显示
* @param {String} type 触发弹层显示或隐藏的来源 fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发
* @param {Object} e DOM事件
*/
onVisibleChange: PropTypes.func,
/**
* 设置此属性,弹层无法显示或隐藏
*/
disabled: PropTypes.bool,
autoFit: PropTypes.bool,
/**
* 弹层显示或隐藏的延时时间(以毫秒为单位),在 triggerType 被设置为 hover 时生效
*/
delay: PropTypes.number,
/**
* trigger 是否可以关闭弹层
*/
canCloseByTrigger: PropTypes.bool,
/**
* 弹层定位的参照元素
* @default target 属性,即触发元素
*/
target: PropTypes.any,
safeNode: PropTypes.any,
/**
* 是否跟随trigger滚动
*/
followTrigger: PropTypes.bool,
container: PropTypes.any,
hasMask: PropTypes.bool,
wrapperStyle: PropTypes.object,
rtl: PropTypes.bool,
/**
* 开启 v2 版本
*/
v2: PropTypes.bool,
/**
* [v2] 快捷位置,包含 'tl' | 't' | 'tr' | 'rt' | 'r' | 'rb' | 'bl' | 'b' | 'br' | 'lt' | 'l' | 'lb'
*/
placement: PropTypes.string,
/**
* [v2] 弹层偏离触发元素的像素值
*/
placementOffset: PropTypes.number
}, _class.defaultProps = {
triggerType: 'hover',
triggerClickKeycode: [KEYCODE.SPACE, KEYCODE.ENTER],
defaultVisible: false,
onVisibleChange: noop,
disabled: false,
autoFit: false,
delay: 200,
canCloseByTrigger: true,
followTrigger: false,
container: function container() {
return document.body;
},
rtl: false
}, _temp);
Popup.displayName = 'Popup';
export default polyfill(Popup);