UNPKG

13.3 kBJavaScriptView Raw
1import _objectWithoutProperties from 'babel-runtime/helpers/objectWithoutProperties';
2import _extends from 'babel-runtime/helpers/extends';
3import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
4import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
5import _inherits from 'babel-runtime/helpers/inherits';
6
7var _class, _temp;
8
9import React, { Component, Children } from 'react';
10import { findDOMNode } from 'react-dom';
11import { polyfill } from 'react-lifecycles-compat';
12import PropTypes from 'prop-types';
13import { func, KEYCODE } from '../util';
14import Overlay from './overlay';
15
16var noop = func.noop,
17 makeChain = func.makeChain,
18 bindCtx = func.bindCtx;
19
20/**
21 * Overlay.Popup
22 * @description 继承 Overlay 的 API,除非特别说明
23 * */
24
25var Popup = (_temp = _class = function (_Component) {
26 _inherits(Popup, _Component);
27
28 function Popup(props) {
29 _classCallCheck(this, Popup);
30
31 var _this = _possibleConstructorReturn(this, _Component.call(this, props));
32
33 _this.state = {
34 visible: typeof props.visible === 'undefined' ? props.defaultVisible : props.visible
35 };
36
37 bindCtx(_this, ['handleTriggerClick', 'handleTriggerKeyDown', 'handleTriggerMouseEnter', 'handleTriggerMouseLeave', 'handleTriggerFocus', 'handleTriggerBlur', 'handleContentMouseEnter', 'handleContentMouseLeave', 'handleContentMouseDown', 'handleRequestClose', 'handleMaskMouseEnter', 'handleMaskMouseLeave']);
38 return _this;
39 }
40
41 Popup.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) {
42 if ('visible' in nextProps) {
43 return _extends({}, prevState, {
44 visible: nextProps.visible
45 });
46 }
47
48 return null;
49 };
50
51 Popup.prototype.componentWillUnmount = function componentWillUnmount() {
52 var _this2 = this;
53
54 ['_timer', '_hideTimer', '_showTimer'].forEach(function (time) {
55 _this2[time] && clearTimeout(_this2[time]);
56 });
57 };
58
59 Popup.prototype.handleVisibleChange = function handleVisibleChange(visible, type, e) {
60 if (!('visible' in this.props)) {
61 this.setState({
62 visible: visible
63 });
64 }
65
66 this.props.onVisibleChange(visible, type, e);
67 };
68
69 Popup.prototype.handleTriggerClick = function handleTriggerClick(e) {
70 if (this.state.visible && !this.props.canCloseByTrigger) {
71 return;
72 }
73
74 this.handleVisibleChange(!this.state.visible, 'fromTrigger', e);
75 };
76
77 Popup.prototype.handleTriggerKeyDown = function handleTriggerKeyDown(e) {
78 var triggerClickKeycode = this.props.triggerClickKeycode;
79
80 var keycodes = Array.isArray(triggerClickKeycode) ? triggerClickKeycode : [triggerClickKeycode];
81 if (keycodes.includes(e.keyCode)) {
82 e.preventDefault();
83 this.handleTriggerClick(e);
84 }
85 };
86
87 Popup.prototype.handleTriggerMouseEnter = function handleTriggerMouseEnter(e) {
88 var _this3 = this;
89
90 this._mouseNotFirstOnMask = false;
91
92 if (this._hideTimer) {
93 clearTimeout(this._hideTimer);
94 this._hideTimer = null;
95 }
96 if (this._showTimer) {
97 clearTimeout(this._showTimer);
98 this._showTimer = null;
99 }
100 if (!this.state.visible) {
101 this._showTimer = setTimeout(function () {
102 _this3.handleVisibleChange(true, 'fromTrigger', e);
103 }, this.props.delay);
104 }
105 };
106
107 Popup.prototype.handleTriggerMouseLeave = function handleTriggerMouseLeave(e, type) {
108 var _this4 = this;
109
110 if (this._showTimer) {
111 clearTimeout(this._showTimer);
112 this._showTimer = null;
113 }
114 if (this.state.visible) {
115 this._hideTimer = setTimeout(function () {
116 _this4.handleVisibleChange(false, type || 'fromTrigger', e);
117 }, this.props.delay);
118 }
119 };
120
121 Popup.prototype.handleTriggerFocus = function handleTriggerFocus(e) {
122 this.handleVisibleChange(true, 'fromTrigger', e);
123 };
124
125 Popup.prototype.handleTriggerBlur = function handleTriggerBlur(e) {
126 if (!this._isForwardContent) {
127 this.handleVisibleChange(false, 'fromTrigger', e);
128 }
129 this._isForwardContent = false;
130 };
131
132 Popup.prototype.handleContentMouseDown = function handleContentMouseDown() {
133 this._isForwardContent = true;
134 };
135
136 Popup.prototype.handleContentMouseEnter = function handleContentMouseEnter() {
137 clearTimeout(this._hideTimer);
138 };
139
140 Popup.prototype.handleContentMouseLeave = function handleContentMouseLeave(e) {
141 this.handleTriggerMouseLeave(e, 'fromContent');
142 };
143
144 Popup.prototype.handleMaskMouseEnter = function handleMaskMouseEnter() {
145 if (!this._mouseNotFirstOnMask) {
146 clearTimeout(this._hideTimer);
147 this._hideTimer = null;
148 this._mouseNotFirstOnMask = false;
149 }
150 };
151
152 Popup.prototype.handleMaskMouseLeave = function handleMaskMouseLeave() {
153 this._mouseNotFirstOnMask = true;
154 };
155
156 Popup.prototype.handleRequestClose = function handleRequestClose(type, e) {
157 this.handleVisibleChange(false, type, e);
158 };
159
160 Popup.prototype.renderTrigger = function renderTrigger() {
161 var _this5 = this;
162
163 var _props = this.props,
164 trigger = _props.trigger,
165 disabled = _props.disabled;
166
167 var props = {
168 key: 'trigger',
169 'aria-haspopup': true,
170 'aria-expanded': this.state.visible
171 };
172
173 if (!this.state.visible) {
174 props['aria-describedby'] = undefined;
175 }
176
177 if (!disabled) {
178 var triggerType = this.props.triggerType;
179
180 var triggerTypes = Array.isArray(triggerType) ? triggerType : [triggerType];
181
182 var _ref = trigger && trigger.props || {},
183 onClick = _ref.onClick,
184 onKeyDown = _ref.onKeyDown,
185 onMouseEnter = _ref.onMouseEnter,
186 onMouseLeave = _ref.onMouseLeave,
187 onFocus = _ref.onFocus,
188 onBlur = _ref.onBlur;
189
190 triggerTypes.forEach(function (triggerType) {
191 switch (triggerType) {
192 case 'click':
193 props.onClick = makeChain(_this5.handleTriggerClick, onClick);
194 props.onKeyDown = makeChain(_this5.handleTriggerKeyDown, onKeyDown);
195 break;
196 case 'hover':
197 props.onMouseEnter = makeChain(_this5.handleTriggerMouseEnter, onMouseEnter);
198 props.onMouseLeave = makeChain(_this5.handleTriggerMouseLeave, onMouseLeave);
199 break;
200 case 'focus':
201 props.onFocus = makeChain(_this5.handleTriggerFocus, onFocus);
202 props.onBlur = makeChain(_this5.handleTriggerBlur, onBlur);
203 break;
204 default:
205 break;
206 }
207 });
208 }
209
210 return trigger && React.cloneElement(trigger, props);
211 };
212
213 Popup.prototype.renderContent = function renderContent() {
214 var _this6 = this;
215
216 var _props2 = this.props,
217 children = _props2.children,
218 triggerType = _props2.triggerType;
219
220 var triggerTypes = Array.isArray(triggerType) ? triggerType : [triggerType];
221 var content = Children.only(children);
222 var _content$props = content.props,
223 onMouseDown = _content$props.onMouseDown,
224 onMouseEnter = _content$props.onMouseEnter,
225 onMouseLeave = _content$props.onMouseLeave;
226
227 var props = {
228 key: 'portal'
229 };
230
231 triggerTypes.forEach(function (triggerType) {
232 switch (triggerType) {
233 case 'focus':
234 props.onMouseDown = makeChain(_this6.handleContentMouseDown, onMouseDown);
235 break;
236 case 'hover':
237 props.onMouseEnter = makeChain(_this6.handleContentMouseEnter, onMouseEnter);
238 props.onMouseLeave = makeChain(_this6.handleContentMouseLeave, onMouseLeave);
239 break;
240 default:
241 break;
242 }
243 });
244
245 return React.cloneElement(content, props);
246 };
247
248 Popup.prototype.renderPortal = function renderPortal() {
249 var _this7 = this;
250
251 var _props3 = this.props,
252 target = _props3.target,
253 safeNode = _props3.safeNode,
254 followTrigger = _props3.followTrigger,
255 triggerType = _props3.triggerType,
256 hasMask = _props3.hasMask,
257 wrapperStyle = _props3.wrapperStyle,
258 others = _objectWithoutProperties(_props3, ['target', 'safeNode', 'followTrigger', 'triggerType', 'hasMask', 'wrapperStyle']);
259
260 var container = this.props.container;
261
262 var findTriggerNode = function findTriggerNode() {
263 return findDOMNode(_this7);
264 };
265 var safeNodes = Array.isArray(safeNode) ? [].concat(safeNode) : [safeNode];
266 safeNodes.unshift(findTriggerNode);
267
268 var newWrapperStyle = wrapperStyle || {};
269
270 if (followTrigger) {
271 container = function container(trigger) {
272 return trigger && trigger.parentNode || trigger;
273 };
274 newWrapperStyle.position = 'relative';
275 }
276
277 if (triggerType === 'hover' && hasMask) {
278 others.onMaskMouseEnter = this.handleMaskMouseEnter;
279 others.onMaskMouseLeave = this.handleMaskMouseLeave;
280 }
281
282 return React.createElement(
283 Overlay,
284 _extends({}, others, {
285 key: 'overlay',
286 ref: function ref(overlay) {
287 return _this7.overlay = overlay;
288 },
289 visible: this.state.visible,
290 target: target || findTriggerNode,
291 container: container,
292 safeNode: safeNodes,
293 wrapperStyle: newWrapperStyle,
294 triggerType: triggerType,
295 hasMask: hasMask,
296 onRequestClose: this.handleRequestClose
297 }),
298 this.props.children && this.renderContent()
299 );
300 };
301
302 Popup.prototype.render = function render() {
303 return [this.renderTrigger(), this.renderPortal()];
304 };
305
306 return Popup;
307}(Component), _class.propTypes = {
308 /**
309 * 弹层内容
310 */
311 children: PropTypes.node,
312 /**
313 * 触发弹层显示或隐藏的元素
314 */
315 trigger: PropTypes.element,
316 /**
317 * 触发弹层显示或隐藏的操作类型,可以是 'click','hover','focus',或者它们组成的数组,如 ['hover', 'focus']
318 */
319 triggerType: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
320 /**
321 * 当 triggerType 为 click 时才生效,可自定义触发弹层显示的键盘码
322 */
323 triggerClickKeycode: PropTypes.oneOfType([PropTypes.number, PropTypes.array]),
324 /**
325 * 弹层当前是否显示
326 */
327 visible: PropTypes.bool,
328 /**
329 * 弹层默认是否显示
330 */
331 defaultVisible: PropTypes.bool,
332 /**
333 * 弹层显示或隐藏时触发的回调函数
334 * @param {Boolean} visible 弹层是否显示
335 * @param {String} type 触发弹层显示或隐藏的来源 fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发
336 * @param {Object} e DOM事件
337 */
338 onVisibleChange: PropTypes.func,
339 /**
340 * 设置此属性,弹层无法显示或隐藏
341 */
342 disabled: PropTypes.bool,
343 autoFit: PropTypes.bool,
344 /**
345 * 弹层显示或隐藏的延时时间(以毫秒为单位),在 triggerType 被设置为 hover 时生效
346 */
347 delay: PropTypes.number,
348 /**
349 * trigger 是否可以关闭弹层
350 */
351 canCloseByTrigger: PropTypes.bool,
352 /**
353 * 弹层定位的参照元素
354 * @default target 属性,即触发元素
355 */
356 target: PropTypes.any,
357 safeNode: PropTypes.any,
358 /**
359 * 是否跟随trigger滚动
360 */
361 followTrigger: PropTypes.bool,
362 container: PropTypes.any,
363 hasMask: PropTypes.bool,
364 wrapperStyle: PropTypes.object,
365 rtl: PropTypes.bool,
366 /**
367 * 开启 v2 版本
368 */
369 v2: PropTypes.bool,
370 /**
371 * [v2] 快捷位置,包含 'tl' | 't' | 'tr' | 'rt' | 'r' | 'rb' | 'bl' | 'b' | 'br' | 'lt' | 'l' | 'lb'
372 */
373 placement: PropTypes.string,
374 /**
375 * [v2] 弹层偏离触发元素的像素值
376 */
377 placementOffset: PropTypes.number
378}, _class.defaultProps = {
379 triggerType: 'hover',
380 triggerClickKeycode: [KEYCODE.SPACE, KEYCODE.ENTER],
381 defaultVisible: false,
382 onVisibleChange: noop,
383 disabled: false,
384 autoFit: false,
385 delay: 200,
386 canCloseByTrigger: true,
387 followTrigger: false,
388 container: function container() {
389 return document.body;
390 },
391 rtl: false
392}, _temp);
393Popup.displayName = 'Popup';
394
395
396export default polyfill(Popup);
\No newline at end of file