zent
Version:
一套前端设计语言和基于React的实现
472 lines (370 loc) • 14.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PopoverContextType = undefined;
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _noop = require('lodash/noop');
var _noop2 = _interopRequireDefault(_noop);
var _uniqueId = require('lodash/uniqueId');
var _uniqueId2 = _interopRequireDefault(_uniqueId);
var _isFunction = require('lodash/isFunction');
var _isFunction2 = _interopRequireDefault(_isFunction);
var _isBoolean = require('lodash/isBoolean');
var _isBoolean2 = _interopRequireDefault(_isBoolean);
var _isPromise = require('../utils/isPromise');
var _isPromise2 = _interopRequireDefault(_isPromise);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _kindOf = require('../utils/kindOf');
var _kindOf2 = _interopRequireDefault(_kindOf);
var _getWidth = require('../utils/getWidth');
var _getWidth2 = _interopRequireDefault(_getWidth);
var _Content = require('./Content');
var _Content2 = _interopRequireDefault(_Content);
var _Trigger = require('./trigger/Trigger');
var _Trigger2 = _interopRequireDefault(_Trigger);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var SKIPPED = function SKIPPED() {}; /**
* 设计:
*
* Popover组件只是一个壳子,负责组装Trigger和Content。
*
* 弹层实际的打开/关闭都是Content完成的,而什么情况打开弹层是Trigger控制的。
*
* Popover 组件是一个递归的组件,支持嵌套。
*
*
* context context
* ------> ------>
* Popover Popover child Popover grand-child ......
* <------ <------
* isOutsideStacked isOutsideStacked
*
*/
function handleBeforeHook(beforeFn, arity, continuation, escape) {
// 有参数,传入continuation,由外部去控制何时调用
// escapse 用来终止 onChange 操作
if (arity === 1) {
return beforeFn(continuation);
} else if (arity >= 2) {
return beforeFn(continuation, escape);
}
// 无参数,如果返回Promise那么resolve后调用continuation, reject 的话调用 escape;
// 如果返回不是Promise,直接调用Promise
var mayBePromise = beforeFn();
if (!(0, _isPromise2['default'])(mayBePromise) && mayBePromise !== SKIPPED) {
return continuation();
}
mayBePromise.then(continuation, escape);
}
var PopoverContextType = exports.PopoverContextType = {
_zentPopover: _propTypes2['default'].shape({
close: _propTypes2['default'].func.isRequired,
open: _propTypes2['default'].func.isRequired,
getContentNode: _propTypes2['default'].func.isRequired,
getTriggerNode: _propTypes2['default'].func.isRequired,
// 用于维护 Popover 栈,处理嵌套的问题
registerDescendant: _propTypes2['default'].func,
unregisterDescendant: _propTypes2['default'].func
})
};
var Popover = function (_ref) {
(0, _inherits3['default'])(Popover, _ref);
(0, _createClass3['default'])(Popover, [{
key: 'getChildContext',
value: function getChildContext() {
return {
_zentPopover: {
close: this.close,
open: this.open,
getContentNode: this.getPopoverNode,
getTriggerNode: this.getTriggerNode,
registerDescendant: this.registerDescendant,
unregisterDescendant: this.unregisterDescendant
}
};
}
}]);
function Popover(props) {
(0, _classCallCheck3['default'])(this, Popover);
// id用来唯一标识popover实例
var _this = (0, _possibleConstructorReturn3['default'])(this, (Popover.__proto__ || Object.getPrototypeOf(Popover)).call(this, props));
_initialiseProps.call(_this);
_this.id = (0, _uniqueId2['default'])(props.prefix + '-popover-internal-id-');
// 记录 Popover 子孙
_this.descendants = [];
if (!_this.isVisibilityControlled(props)) {
_this.state = {
visible: false
};
}
_this.isUnmounted = false;
return _this;
}
(0, _createClass3['default'])(Popover, [{
key: 'isVisibilityControlled',
value: function isVisibilityControlled(props) {
var _ref2 = props || this.props,
visible = _ref2.visible,
onVisibleChange = _ref2.onVisibleChange;
var hasOnChange = (0, _isFunction2['default'])(onVisibleChange);
var hasVisible = (0, _isBoolean2['default'])(visible);
if (hasVisible && !hasOnChange || hasOnChange && !hasVisible) {
throw new Error('visible and onVisibleChange must be used together');
}
return hasVisible && hasOnChange;
}
// Popover up in the tree will call this method to see if the node lies outside
}, {
key: 'validateChildren',
value: function validateChildren() {
var children = this.props.children;
var childArray = _react.Children.toArray(children);
if (childArray.length !== 2) {
throw new Error('There must be one and only one trigger and content in Popover');
}
var _childArray$reduce = childArray.reduce(function (state, c) {
var type = c.type;
if ((0, _kindOf2['default'])(type, _Trigger2['default'])) {
state.trigger = c;
} else if ((0, _kindOf2['default'])(type, _Content2['default'])) {
state.content = c;
}
return state;
}, { trigger: null, content: null }),
trigger = _childArray$reduce.trigger,
content = _childArray$reduce.content;
if (!trigger) {
throw new Error('Missing trigger in Popover');
}
if (!content) {
throw new Error('Missing content in Popover');
}
return { trigger: trigger, content: content };
}
}, {
key: 'safeSetState',
value: function safeSetState(updater, callback) {
if (!this.isUnmounted) {
return this.setState(updater, callback);
}
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
var _ref3 = this.context || {},
popover = _ref3._zentPopover;
if (popover && popover.registerDescendant) {
popover.registerDescendant(this);
}
if (this.isVisibilityControlled() && this.props.visible) {
this.props.onShow();
}
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
var visible = this.getVisible();
if (visible !== this.getVisible(prevProps, prevState)) {
var afterHook = visible ? this.props.onShow : this.props.onClose;
afterHook();
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
var _ref4 = this.context || {},
popover = _ref4._zentPopover;
if (popover && popover.unregisterDescendant) {
popover.unregisterDescendant(this);
}
this.isUnmounted = true;
}
}, {
key: 'render',
value: function render() {
var _validateChildren = this.validateChildren(),
trigger = _validateChildren.trigger,
content = _validateChildren.content;
var _props = this.props,
display = _props.display,
prefix = _props.prefix,
className = _props.className,
wrapperClassName = _props.wrapperClassName,
containerSelector = _props.containerSelector,
position = _props.position,
cushion = _props.cushion,
width = _props.width;
var visible = this.getVisible();
return _react2['default'].createElement(
'div',
{
style: (0, _extends3['default'])({ display: display }, (0, _getWidth2['default'])(width)),
className: (0, _classnames2['default'])(prefix + '-popover-wrapper', wrapperClassName)
},
_react2['default'].cloneElement(trigger, {
prefix: prefix,
contentVisible: visible,
onTriggerRefChange: this.onTriggerRefChange,
getTriggerNode: this.getTriggerNode,
getContentNode: this.getPopoverNode,
open: this.open,
close: this.close,
isOutsideStacked: this.isOutsideStacked,
injectIsOutsideSelf: this.injectIsOutsideSelf
}),
_react2['default'].cloneElement(content, {
prefix: prefix,
className: className,
id: this.id,
getContentNode: this.getPopoverNode,
getAnchor: this.getTriggerNode,
visible: visible,
cushion: cushion,
containerSelector: containerSelector,
placement: position
})
);
}
}]);
return Popover;
}(_react.PureComponent || _react.Component);
Popover.propTypes = {
prefix: _propTypes2['default'].string,
className: _propTypes2['default'].string,
// custom classname for trigger wrapper
wrapperClassName: _propTypes2['default'].string,
// container的display属性
display: _propTypes2['default'].string,
// position strategy
position: _propTypes2['default'].func.isRequired,
// 定位时的偏移量
cushion: _propTypes2['default'].number,
// 只有用户触发的打开/关闭才会触发这两个毁掉
onBeforeClose: _propTypes2['default'].func,
onBeforeShow: _propTypes2['default'].func,
// 不管打开/关闭时如何触发的都会被调用
onClose: _propTypes2['default'].func,
onShow: _propTypes2['default'].func,
// defaults to body
containerSelector: _propTypes2['default'].string,
children: _propTypes2['default'].node.isRequired,
// 两个必须一起出现
visible: _propTypes2['default'].bool,
onVisibleChange: _propTypes2['default'].func
};
Popover.defaultProps = {
prefix: 'zent',
className: '',
wrapperClassName: '',
display: 'block',
onBeforeClose: _noop2['default'],
onBeforeShow: _noop2['default'],
onClose: _noop2['default'],
onShow: _noop2['default'],
cushion: 0,
containerSelector: 'body'
};
Popover.contextTypes = PopoverContextType;
Popover.childContextTypes = PopoverContextType;
var _initialiseProps = function _initialiseProps() {
var _this2 = this;
this.registerDescendant = function (popover) {
_this2.descendants.push(popover);
};
this.unregisterDescendant = function (popover) {
var idx = _this2.descendants.indexOf(popover);
_this2.descendants.splice(idx, 1);
};
this.getVisible = function (props, state) {
if (_this2.isVisibilityControlled(props)) {
props = props || _this2.props;
return props.visible;
}
state = state || _this2.state;
return state.visible;
};
this.setVisible = function (visible, props, state) {
props = props || _this2.props;
state = state || _this2.state;
var beforeHook = visible ? props.onBeforeShow : props.onBeforeClose;
var onBefore = function onBefore() {
// 确保pending的时候不会触发多次beforeHook
if (_this2.pendingOnBeforeHook) {
return SKIPPED;
}
_this2.pendingOnBeforeHook = true;
return beforeHook.apply(undefined, arguments);
};
var escapse = function escapse() {
_this2.pendingOnBeforeHook = false;
};
if (_this2.isVisibilityControlled(props)) {
if (_this2.pendingOnBeforeHook || props.visible === visible) {
return;
}
handleBeforeHook(onBefore, beforeHook.length, function () {
props.onVisibleChange(visible);
_this2.pendingOnBeforeHook = false;
}, escapse);
} else {
if (_this2.pendingOnBeforeHook || state.visible === visible) {
return;
}
handleBeforeHook(onBefore, beforeHook.length, function () {
_this2.safeSetState({ visible: visible });
_this2.pendingOnBeforeHook = false;
}, escapse);
}
};
this.getPopoverNode = function () {
return document.querySelector('.' + _this2.id);
};
this.onTriggerRefChange = function (triggerInstance) {
_this2.triggerNode = _reactDom2['default'].findDOMNode(triggerInstance);
};
this.getTriggerNode = function () {
return _this2.triggerNode;
};
this.open = function () {
_this2.setVisible(true);
};
this.close = function () {
_this2.setVisible(false);
};
this.injectIsOutsideSelf = function (impl) {
_this2.isOutsideSelf = impl;
};
this.isOutsideStacked = function (node) {
if (_this2.isOutsideSelf) {
// 在自身内部,肯定不在外面
if (!_this2.isOutsideSelf(node)) {
return false;
}
}
// 问下面的 Popover 是否在外面
if (_this2.descendants.some(function (popover) {
return !popover.isOutsideStacked(node);
})) {
return false;
}
return true;
};
};
exports['default'] = Popover;