UNPKG

17.4 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 } from 'react';
10import PropTypes from 'prop-types';
11import Overlay from '../overlay';
12import zhCN from '../locale/zh-cn';
13import { focus, obj, func, events, dom, env } from '../util';
14import Inner from './inner';
15
16var noop = function noop() {};
17var limitTabRange = focus.limitTabRange;
18var bindCtx = func.bindCtx;
19var pickOthers = obj.pickOthers;
20var getStyle = dom.getStyle,
21 setStyle = dom.setStyle;
22
23// [fix issue #1609](https://github.com/alibaba-fusion/next/issues/1609)
24// https://stackoverflow.com/questions/19717907/getcomputedstyle-reporting-different-heights-between-chrome-safari-firefox-and-i
25
26function _getSize(dom, name) {
27 var boxSizing = getStyle(dom, 'boxSizing');
28
29 if (env.ieVersion && ['width', 'height'].indexOf(name) !== -1 && boxSizing === 'border-box') {
30 return parseFloat(dom.getBoundingClientRect()[name].toFixed(1));
31 } else {
32 return getStyle(dom, name);
33 }
34}
35
36/**
37 * Dialog
38 */
39var Dialog = (_temp = _class = function (_Component) {
40 _inherits(Dialog, _Component);
41
42 function Dialog(props, context) {
43 _classCallCheck(this, Dialog);
44
45 var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
46
47 bindCtx(_this, ['onKeyDown', 'beforePosition', 'adjustPosition', 'getOverlayRef']);
48 return _this;
49 }
50
51 Dialog.prototype.componentDidMount = function componentDidMount() {
52 events.on(document, 'keydown', this.onKeyDown);
53 if (!this.useCSSToPosition()) {
54 this.adjustPosition();
55 }
56 };
57
58 Dialog.prototype.componentWillUnmount = function componentWillUnmount() {
59 events.off(document, 'keydown', this.onKeyDown);
60 };
61
62 Dialog.prototype.useCSSToPosition = function useCSSToPosition() {
63 var _props = this.props,
64 align = _props.align,
65 isFullScreen = _props.isFullScreen;
66
67 return align === 'cc cc' && isFullScreen;
68 };
69
70 Dialog.prototype.onKeyDown = function onKeyDown(e) {
71 var node = this.getInnerNode();
72 if (node) {
73 limitTabRange(node, e);
74 }
75 };
76
77 Dialog.prototype.beforePosition = function beforePosition() {
78 if (this.props.visible && this.overlay) {
79 var inner = this.getInner();
80 if (inner) {
81 var node = this.getInnerNode();
82 if (this._lastDialogHeight !== _getSize(node, 'height')) {
83 this.revertSize(inner.bodyNode);
84 }
85 }
86 }
87 };
88
89 Dialog.prototype.adjustPosition = function adjustPosition() {
90 if (this.props.visible && this.overlay) {
91 var inner = this.getInner();
92 if (inner) {
93 var node = this.getInnerNode();
94
95 var top = getStyle(node, 'top');
96 var minMargin = this.props.minMargin;
97 if (top < minMargin) {
98 top = minMargin;
99 setStyle(node, 'top', minMargin + 'px');
100 }
101
102 var height = _getSize(node, 'height');
103 var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
104
105 if (viewportHeight < height + top * 2 - 1 || // 分辨率和精确度的原因 高度计算的时候 可能会有1px内的偏差
106 this.props.height) {
107 this.adjustSize(inner, node, Math.min(height, viewportHeight - top * 2));
108 } else {
109 this.revertSize(inner.bodyNode);
110 }
111
112 this._lastDialogHeight = height;
113 }
114 }
115 };
116
117 Dialog.prototype.adjustSize = function adjustSize(inner, node, expectHeight) {
118 var headerNode = inner.headerNode,
119 bodyNode = inner.bodyNode,
120 footerNode = inner.footerNode;
121
122 var _map = [headerNode, footerNode].map(function (node) {
123 return node ? _getSize(node, 'height') : 0;
124 }),
125 headerHeight = _map[0],
126 footerHeight = _map[1];
127
128 var padding = ['padding-top', 'padding-bottom'].reduce(function (sum, attr) {
129 return sum + getStyle(node, attr);
130 }, 0);
131
132 var maxBodyHeight = expectHeight - headerHeight - footerHeight - padding;
133
134 if (maxBodyHeight < 0) {
135 maxBodyHeight = 1;
136 }
137
138 if (bodyNode) {
139 this.dialogBodyStyleMaxHeight = bodyNode.style.maxHeight;
140 this.dialogBodyStyleOverflowY = bodyNode.style.overflowY;
141
142 setStyle(bodyNode, {
143 'max-height': maxBodyHeight + 'px',
144 'overflow-y': 'auto'
145 });
146 }
147 };
148
149 Dialog.prototype.revertSize = function revertSize(bodyNode) {
150 setStyle(bodyNode, {
151 'max-height': this.dialogBodyStyleMaxHeight,
152 'overflow-y': this.dialogBodyStyleOverflowY
153 });
154 };
155
156 Dialog.prototype.mapcloseableToConfig = function mapcloseableToConfig(closeable) {
157 return ['esc', 'close', 'mask'].reduce(function (ret, option) {
158 var key = option.charAt(0).toUpperCase() + option.substr(1);
159 var value = typeof closeable === 'boolean' ? closeable : closeable.split(',').indexOf(option) > -1;
160
161 if (option === 'esc' || option === 'mask') {
162 ret['canCloseBy' + key] = value;
163 } else {
164 ret['canCloseBy' + key + 'Click'] = value;
165 }
166
167 return ret;
168 }, {});
169 };
170
171 Dialog.prototype.getOverlayRef = function getOverlayRef(ref) {
172 this.overlay = ref;
173 };
174
175 Dialog.prototype.getInner = function getInner() {
176 return this.overlay.getInstance().getContent();
177 };
178
179 Dialog.prototype.getInnerNode = function getInnerNode() {
180 return this.overlay.getInstance().getContentNode();
181 };
182
183 Dialog.prototype.renderInner = function renderInner(closeable) {
184 var _props2 = this.props,
185 prefix = _props2.prefix,
186 className = _props2.className,
187 title = _props2.title,
188 children = _props2.children,
189 footer = _props2.footer,
190 footerAlign = _props2.footerAlign,
191 footerActions = _props2.footerActions,
192 onOk = _props2.onOk,
193 onCancel = _props2.onCancel,
194 okProps = _props2.okProps,
195 cancelProps = _props2.cancelProps,
196 onClose = _props2.onClose,
197 locale = _props2.locale,
198 visible = _props2.visible,
199 rtl = _props2.rtl,
200 height = _props2.height;
201
202 var others = pickOthers(Object.keys(Dialog.propTypes), this.props);
203
204 return React.createElement(
205 Inner,
206 _extends({
207 prefix: prefix,
208 className: className,
209 title: title,
210 footer: footer,
211 footerAlign: footerAlign,
212 footerActions: footerActions,
213 onOk: visible ? onOk : noop,
214 onCancel: visible ? onCancel : noop,
215 okProps: okProps,
216 cancelProps: cancelProps,
217 locale: locale,
218 closeable: closeable,
219 rtl: rtl,
220 onClose: onClose.bind(this, 'closeClick'),
221 height: height
222 }, others),
223 children
224 );
225 };
226
227 Dialog.prototype.render = function render() {
228 var _props3 = this.props,
229 prefix = _props3.prefix,
230 visible = _props3.visible,
231 hasMask = _props3.hasMask,
232 animation = _props3.animation,
233 autoFocus = _props3.autoFocus,
234 closeable = _props3.closeable,
235 closeMode = _props3.closeMode,
236 onClose = _props3.onClose,
237 afterClose = _props3.afterClose,
238 shouldUpdatePosition = _props3.shouldUpdatePosition,
239 align = _props3.align,
240 popupContainer = _props3.popupContainer,
241 cache = _props3.cache,
242 overlayProps = _props3.overlayProps,
243 rtl = _props3.rtl;
244
245
246 var useCSS = this.useCSSToPosition();
247 var newCloseable = 'closeMode' in this.props ? Array.isArray(closeMode) ? closeMode.join(',') : closeMode : closeable;
248
249 var _mapcloseableToConfig = this.mapcloseableToConfig(newCloseable),
250 canCloseByCloseClick = _mapcloseableToConfig.canCloseByCloseClick,
251 closeConfig = _objectWithoutProperties(_mapcloseableToConfig, ['canCloseByCloseClick']);
252
253 var newOverlayProps = _extends({
254 disableScroll: true,
255 container: popupContainer,
256 cache: cache
257 }, overlayProps, {
258 prefix: prefix,
259 visible: visible,
260 animation: animation,
261 hasMask: hasMask,
262 autoFocus: autoFocus,
263 afterClose: afterClose
264 }, closeConfig, {
265 canCloseByOutSideClick: false,
266 align: useCSS ? false : align,
267 onRequestClose: onClose,
268 needAdjust: false,
269 ref: this.getOverlayRef,
270 rtl: rtl,
271 maskClass: useCSS ? prefix + 'dialog-container' : '',
272 isChildrenInMask: useCSS && hasMask
273 });
274 if (!useCSS) {
275 newOverlayProps.beforePosition = this.beforePosition;
276 newOverlayProps.onPosition = this.adjustPosition;
277 newOverlayProps.shouldUpdatePosition = shouldUpdatePosition;
278 }
279
280 var inner = this.renderInner(canCloseByCloseClick);
281
282 // useCSS && hasMask : isFullScreen 并且 有mask的模式下,为了解决 next-overlay-backdrop 覆盖mask,使得点击mask关闭页面的功能不生效的问题,需要开启 Overlay 的 isChildrenInMask 功能,并且把 next-dialog-container 放到 next-overlay-backdrop上
283 // useCSS && !hasMask : isFullScreen 并且 没有mask的情况下,需要关闭 isChildrenInMask 功能,以防止children不渲染
284 // 其他模式下维持 mask 与 children 同级的关系
285 return React.createElement(
286 Overlay,
287 newOverlayProps,
288 useCSS && !hasMask ? React.createElement(
289 'div',
290 { className: prefix + 'dialog-container', dir: rtl ? 'rtl' : undefined },
291 inner
292 ) : inner
293 );
294 };
295
296 return Dialog;
297}(Component), _class.propTypes = {
298 prefix: PropTypes.string,
299 pure: PropTypes.bool,
300 rtl: PropTypes.bool,
301 className: PropTypes.string,
302 /**
303 * 是否显示
304 */
305 visible: PropTypes.bool,
306 /**
307 * 标题
308 */
309 title: PropTypes.node,
310 /**
311 * 内容
312 */
313 children: PropTypes.node,
314 /**
315 * 底部内容,设置为 false,则不进行显示
316 * @default [<Button type="primary">确定</Button>, <Button>取消</Button>]
317 */
318 footer: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
319 /**
320 * 底部按钮的对齐方式
321 */
322 footerAlign: PropTypes.oneOf(['left', 'center', 'right']),
323 /**
324 * 指定确定按钮和取消按钮是否存在以及如何排列,<br><br>**可选值**:
325 * ['ok', 'cancel'](确认取消按钮同时存在,确认按钮在左)
326 * ['cancel', 'ok'](确认取消按钮同时存在,确认按钮在右)
327 * ['ok'](只存在确认按钮)
328 * ['cancel'](只存在取消按钮)
329 */
330 footerActions: PropTypes.array,
331 /**
332 * 在点击确定按钮时触发的回调函数
333 * @param {Object} event 点击事件对象
334 */
335 onOk: PropTypes.func,
336 /**
337 * 在点击取消/关闭按钮时触发的回调函数
338 * @param {Object} event 点击事件对象, event.triggerType=esc|closeIcon 可区分点击来源
339 */
340 onCancel: PropTypes.func,
341 /**
342 * 应用于确定按钮的属性对象
343 */
344 okProps: PropTypes.object,
345 /**
346 * 应用于取消按钮的属性对象
347 */
348 cancelProps: PropTypes.object,
349 /**
350 * [推荐]1.21.x 支持控制对话框关闭的方式,值可以为字符串或者数组,其中字符串、数组均为以下值的枚举:
351 * **close** 表示点击关闭按钮可以关闭对话框
352 * **mask** 表示点击遮罩区域可以关闭对话框
353 * **esc** 表示按下 esc 键可以关闭对话框
354 * 如 'close' 或 ['close','esc','mask'], []
355 * @version 1.21
356 */
357 closeMode: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOf(['close', 'mask', 'esc'])), PropTypes.oneOf(['close', 'mask', 'esc'])]),
358 /**
359 * 隐藏时是否保留子节点,不销毁 (低版本通过 overlayProps 实现)
360 * @version 1.23
361 */
362 cache: PropTypes.bool,
363 /**
364 * 对话框关闭后触发的回调函数, 如果有动画,则在动画结束后触发
365 */
366 afterClose: PropTypes.func,
367 /**
368 * 是否显示遮罩
369 */
370 hasMask: PropTypes.bool,
371 /**
372 * 显示隐藏时动画的播放方式,支持 { in: 'enter-class', out: 'leave-class' } 的对象参数,如果设置为 false,则不播放动画。 请参考 Animate 组件的文档获取可用的动画名
373 * @default { in: 'expandInDown', out: 'expandOutUp' }
374 */
375 animation: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
376 /**
377 * 对话框弹出时是否自动获得焦点
378 */
379 autoFocus: PropTypes.bool,
380 /**
381 * [v2废弃] 透传到弹层组件的属性对象
382 */
383 overlayProps: PropTypes.object,
384 /**
385 * 自定义国际化文案对象
386 * @property {String} ok 确认按钮文案
387 * @property {String} cancel 取消按钮文案
388 */
389 locale: PropTypes.object,
390 // Do not remove this, it's for <ConfigProvider popupContainer={} />
391 // see https://github.com/alibaba-fusion/next/issues/1508
392 /**
393 * 自定义弹窗挂载位置
394 */
395 popupContainer: PropTypes.any,
396 /**
397 * 对话框的高度样式属性
398 */
399 height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
400 /**
401 * 开启 v2 版本弹窗
402 */
403 v2: PropTypes.bool,
404 /**
405 * [v2] 弹窗宽度
406 * @version 1.25
407 */
408 width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
409 /**
410 * [v2] 弹窗上边距。默认 100,设置 centered=true 后默认 40
411 * @version 1.25
412 */
413 top: PropTypes.number,
414 /**
415 * [v2] 弹窗下边距
416 * @version 1.25
417 */
418 bottom: PropTypes.number,
419 /**
420 * [v2] 定制关闭按钮 icon
421 * @version 1.25
422 */
423 closeIcon: PropTypes.node,
424 /**
425 * [v2] 弹窗居中对齐
426 * @version 1.25
427 */
428 centered: PropTypes.bool,
429 /**
430 * [v2] 对话框高度超过浏览器视口高度时,对话框是否展示滚动条。关闭此功后对话框会随高度撑开页面
431 * @version 1.25
432 */
433 overflowScroll: PropTypes.bool,
434 /**
435 * [废弃]同closeMode, 控制对话框关闭的方式,值可以为字符串或者布尔值,其中字符串是由以下值组成:
436 * **close** 表示点击关闭按钮可以关闭对话框
437 * **mask** 表示点击遮罩区域可以关闭对话框
438 * **esc** 表示按下 esc 键可以关闭对话框
439 * 如 'close' 或 'close,esc,mask'
440 * 如果设置为 true,则以上关闭方式全部生效
441 * 如果设置为 false,则以上关闭方式全部失效
442 */
443 closeable: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
444 /**
445 * 点击对话框关闭按钮时触发的回调函数
446 * @param {String} trigger 关闭触发行为的描述字符串
447 * @param {Object} event 关闭时事件对象
448 */
449 onClose: PropTypes.func,
450 /**
451 * [v2废弃] 对话框对齐方式, 具体见Overlay文档
452 */
453 align: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
454 /**
455 * [v2废弃] 是否撑开页面。 v2 改用 overflowScroll
456 */
457 isFullScreen: PropTypes.bool,
458 /**
459 * [v2废弃] 是否在对话框重新渲染时及时更新对话框位置,一般用于对话框高度变化后依然能保证原来的对齐方式
460 */
461 shouldUpdatePosition: PropTypes.bool,
462 /**
463 * [v2废弃] 对话框距离浏览器顶部和底部的最小间距,align 被设置为 'cc cc' 并且 isFullScreen 被设置为 true 时不生效
464 */
465 minMargin: PropTypes.number
466}, _class.defaultProps = {
467 prefix: 'next-',
468 pure: false,
469 visible: false,
470 footerAlign: 'right',
471 footerActions: ['ok', 'cancel'],
472 onOk: noop,
473 onCancel: noop,
474 cache: false,
475 okProps: {},
476 cancelProps: {},
477 closeable: 'esc,close',
478 onClose: noop,
479 afterClose: noop,
480 centered: false,
481 hasMask: true,
482 animation: {
483 in: 'fadeInUp',
484 out: 'fadeOutUp'
485 },
486 autoFocus: false,
487 align: 'cc cc',
488 isFullScreen: false,
489 overflowScroll: true,
490 shouldUpdatePosition: false,
491 minMargin: 40,
492 bottom: 40,
493 overlayProps: {},
494 locale: zhCN.Dialog
495}, _temp);
496Dialog.displayName = 'Dialog';
497export { Dialog as default };
\No newline at end of file