UNPKG

13.9 kBJavaScriptView Raw
1import _extends from 'babel-runtime/helpers/extends';
2import _objectWithoutProperties from 'babel-runtime/helpers/objectWithoutProperties';
3/* istanbul ignore file */
4import React, { useState, useRef, useEffect, useContext } from 'react';
5import ReactDOM from 'react-dom';
6import classNames from 'classnames';
7import Overlay from '@alifd/overlay';
8
9import Inner from './inner';
10import Animate from '../animate';
11import zhCN from '../locale/zh-cn';
12import { log, func, dom, focus, guid } from '../util';
13import scrollLocker from './scroll-locker';
14
15var OverlayContext = Overlay.OverlayContext;
16
17var noop = func.noop;
18
19var Dialog = function Dialog(props) {
20 var _classNames, _classNames2, _classNames3;
21
22 if (!useState || !useRef || !useEffect) {
23 log.warning('need react version > 16.8.0');
24 return null;
25 }
26
27 var _props$prefix = props.prefix,
28 prefix = _props$prefix === undefined ? 'next-' : _props$prefix,
29 _props$afterClose = props.afterClose,
30 afterClose = _props$afterClose === undefined ? noop : _props$afterClose,
31 _props$hasMask = props.hasMask,
32 hasMask = _props$hasMask === undefined ? true : _props$hasMask,
33 _props$autoFocus = props.autoFocus,
34 autoFocus = _props$autoFocus === undefined ? false : _props$autoFocus,
35 className = props.className,
36 title = props.title,
37 children = props.children,
38 footer = props.footer,
39 footerAlign = props.footerAlign,
40 footerActions = props.footerActions,
41 _props$onOk = props.onOk,
42 onOk = _props$onOk === undefined ? noop : _props$onOk,
43 onCancel = props.onCancel,
44 okProps = props.okProps,
45 cancelProps = props.cancelProps,
46 _props$locale = props.locale,
47 locale = _props$locale === undefined ? zhCN.Dialog : _props$locale,
48 rtl = props.rtl,
49 pvisible = props.visible,
50 _props$closeMode = props.closeMode,
51 closeMode = _props$closeMode === undefined ? ['close', 'esc'] : _props$closeMode,
52 closeIcon = props.closeIcon,
53 _props$animation = props.animation,
54 animation = _props$animation === undefined ? { in: 'fadeInUp', out: 'fadeOutUp' } : _props$animation,
55 cache = props.cache,
56 wrapperStyle = props.wrapperStyle,
57 _props$popupContainer = props.popupContainer,
58 popupContainer = _props$popupContainer === undefined ? document.body : _props$popupContainer,
59 dialogRender = props.dialogRender,
60 centered = props.centered,
61 _props$top = props.top,
62 top = _props$top === undefined ? centered ? 40 : 100 : _props$top,
63 _props$bottom = props.bottom,
64 bottom = _props$bottom === undefined ? 40 : _props$bottom,
65 _props$width = props.width,
66 width = _props$width === undefined ? 520 : _props$width,
67 height = props.height,
68 isFullScreen = props.isFullScreen,
69 _props$overflowScroll = props.overflowScroll,
70 overflowScroll = _props$overflowScroll === undefined ? !isFullScreen : _props$overflowScroll,
71 minMargin = props.minMargin,
72 onClose = props.onClose,
73 style = props.style,
74 others = _objectWithoutProperties(props, ['prefix', 'afterClose', 'hasMask', 'autoFocus', 'className', 'title', 'children', 'footer', 'footerAlign', 'footerActions', 'onOk', 'onCancel', 'okProps', 'cancelProps', 'locale', 'rtl', 'visible', 'closeMode', 'closeIcon', 'animation', 'cache', 'wrapperStyle', 'popupContainer', 'dialogRender', 'centered', 'top', 'bottom', 'width', 'height', 'isFullScreen', 'overflowScroll', 'minMargin', 'onClose', 'style']);
75
76 if ('isFullScreen' in props) {
77 log.deprecated('isFullScreen', 'overflowScroll', 'Dialog v2');
78 }
79 if ('minMargin' in props) {
80 log.deprecated('minMargin', 'top/bottom', 'Dialog v2');
81 }
82
83 var _useState = useState(pvisible || false),
84 firstVisible = _useState[0],
85 setFirst = _useState[1];
86
87 var _useState2 = useState(pvisible),
88 visible = _useState2[0],
89 setVisible = _useState2[1];
90
91 var getContainer = typeof popupContainer === 'string' ? function () {
92 return document.getElementById(popupContainer);
93 } : typeof popupContainer !== 'function' ? function () {
94 return popupContainer;
95 } : popupContainer;
96
97 var _useState3 = useState(getContainer()),
98 container = _useState3[0],
99 setContainer = _useState3[1];
100
101 var dialogRef = useRef(null);
102 var wrapperRef = useRef(null);
103 var lastFocus = useRef(null);
104 var locker = useRef(null);
105
106 var _useState4 = useState(guid()),
107 uuid = _useState4[0];
108
109 var _useContext = useContext(OverlayContext),
110 setVisibleOverlayToParent = _useContext.setVisibleOverlayToParent,
111 otherContext = _objectWithoutProperties(_useContext, ['setVisibleOverlayToParent']);
112
113 var childIDMap = useRef(new Map());
114 var isAnimationEnd = useRef(false);
115
116 var _useState5 = useState(),
117 forceUpdate = _useState5[1];
118
119 var markAnimationEnd = function markAnimationEnd(state) {
120 isAnimationEnd.current = state;
121 forceUpdate({});
122 };
123
124 var canCloseByEsc = false;
125 var canCloseByMask = false;
126 var closeable = false;
127
128 var closeModeArray = Array.isArray(closeMode) ? closeMode : [closeMode];
129 closeModeArray.forEach(function (mode) {
130 switch (mode) {
131 case 'esc':
132 canCloseByEsc = true;
133 break;
134 case 'mask':
135 canCloseByMask = true;
136 break;
137 case 'close':
138 closeable = true;
139 break;
140 }
141 });
142
143 // visible 受控
144 useEffect(function () {
145 if ('visible' in props) {
146 setVisible(pvisible);
147 }
148 }, [pvisible]);
149
150 // 打开遮罩后 document.body 滚动处理
151 useEffect(function () {
152 if (visible && hasMask) {
153 var _style = {
154 overflow: 'hidden'
155 };
156
157 if (dom.hasScroll(document.body)) {
158 var scrollWidth = dom.scrollbar().width;
159 if (scrollWidth) {
160 _style.paddingRight = dom.getStyle(document.body, 'paddingRight') + dom.scrollbar().width + 'px';
161 }
162 }
163 locker.current = scrollLocker.lock(document.body, _style);
164 }
165 }, [visible && hasMask]);
166
167 var handleClose = function handleClose(targetType, e) {
168 setVisibleOverlayToParent(uuid, null);
169 typeof onClose === 'function' && onClose(targetType, e);
170 };
171
172 var keydownEvent = function keydownEvent(e) {
173 if (e.keyCode === 27 && canCloseByEsc && !childIDMap.current.size) {
174 handleClose('esc', e);
175 }
176 };
177
178 // esc 键盘事件处理
179 useEffect(function () {
180 if (visible && canCloseByEsc) {
181 document.body.addEventListener('keydown', keydownEvent, false);
182 return function () {
183 document.body.removeEventListener('keydown', keydownEvent, false);
184 };
185 }
186 }, [visible && canCloseByEsc]);
187
188 // 优化: 第一次加载并且 visible=false 的情况不挂载弹窗
189 useEffect(function () {
190 !firstVisible && visible && setFirst(true);
191 }, [visible]);
192
193 // container 异步加载情况
194 useEffect(function () {
195 if (!container) {
196 setTimeout(function () {
197 setContainer(getContainer());
198 });
199 }
200 }, [container]);
201
202 var handleExited = function handleExited() {
203 if (!isAnimationEnd.current) {
204 markAnimationEnd(true);
205 dom.setStyle(wrapperRef.current, 'display', 'none');
206 scrollLocker.unlock(document.body, locker.current);
207
208 if (autoFocus && lastFocus.current) {
209 try {
210 lastFocus.current.focus();
211 } finally {
212 // ignore ...
213 }
214 lastFocus.current = null;
215 }
216 afterClose();
217 }
218 };
219
220 useEffect(function () {
221 return function () {
222 handleExited();
223 };
224 }, []);
225
226 if (firstVisible === false || !container) {
227 return null;
228 }
229
230 if (!visible && !cache && isAnimationEnd.current) {
231 return null;
232 }
233
234 var handleCancel = function handleCancel(e) {
235 if (typeof onCancel === 'function') {
236 onCancel(e);
237 } else {
238 handleClose('cancelBtn', e);
239 }
240 };
241
242 var handleMaskClick = function handleMaskClick(e) {
243 if (!canCloseByMask) {
244 return;
245 }
246
247 if (e.type === 'click' && dialogRef.current) {
248 var dialogNode = ReactDOM.findDOMNode(dialogRef.current);
249 if (dialogNode && dialogNode.contains(e.target)) {
250 return;
251 }
252 }
253
254 handleClose('maskClick', e);
255 };
256
257 var handleEnter = function handleEnter() {
258 markAnimationEnd(false);
259 dom.setStyle(wrapperRef.current, 'display', '');
260 };
261 var handleEntered = function handleEntered() {
262 if (autoFocus && dialogRef.current && dialogRef.current.bodyNode) {
263 var focusableNodes = focus.getFocusNodeList(dialogRef.current.bodyNode);
264 if (focusableNodes.length > 0 && focusableNodes[0]) {
265 lastFocus.current = document.activeElement;
266 focusableNodes[0].focus();
267 }
268 }
269 setVisibleOverlayToParent(uuid, wrapperRef.current);
270 };
271
272 var wrapperCls = classNames((_classNames = {}, _classNames[prefix + 'overlay-wrapper'] = true, _classNames.opened = visible, _classNames));
273 var dialogCls = classNames((_classNames2 = {}, _classNames2[prefix + 'dialog-v2'] = true, _classNames2[className] = !!className, _classNames2));
274
275 var topStyle = {};
276 if (centered) {
277 // 兼容 minMargin
278 if (!top && !bottom && minMargin) {
279 topStyle.marginTop = minMargin;
280 topStyle.marginBottom = minMargin;
281 } else {
282 top && (topStyle.marginTop = top);
283 bottom && (topStyle.marginBottom = bottom);
284 }
285 } else {
286 top && (topStyle.top = top);
287 bottom && (topStyle.paddingBottom = bottom);
288 }
289
290 var maxHeight = void 0;
291 if (overflowScroll) {
292 maxHeight = 'calc(100vh - ' + (top + bottom) + 'px)';
293 }
294
295 var timeout = {
296 appear: 300,
297 enter: 300,
298 exit: 250
299 };
300
301 var inner = React.createElement(
302 Animate.OverlayAnimate,
303 {
304 visible: visible,
305 animation: animation,
306 timeout: timeout,
307 onEnter: handleEnter,
308 onEntered: handleEntered,
309 onExited: handleExited
310 },
311 React.createElement(
312 Inner,
313 _extends({}, others, {
314 style: centered ? _extends({}, topStyle, style) : style,
315 v2: true,
316 ref: dialogRef,
317 prefix: prefix,
318 className: dialogCls,
319 title: title,
320 footer: footer,
321 footerAlign: footerAlign,
322 footerActions: footerActions,
323 onOk: visible ? onOk : noop,
324 onCancel: visible ? handleCancel : noop,
325 okProps: okProps,
326 cancelProps: cancelProps,
327 locale: locale,
328 closeable: closeable,
329 rtl: rtl,
330 onClose: function onClose() {
331 for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
332 args[_key] = arguments[_key];
333 }
334
335 return handleClose.apply(undefined, ['closeClick'].concat(args));
336 },
337 closeIcon: closeIcon,
338 height: height,
339 maxHeight: maxHeight,
340 width: width
341 }),
342 children
343 )
344 );
345
346 if (typeof dialogRender === 'function') {
347 inner = dialogRender(inner);
348 }
349
350 var innerWrapperCls = classNames((_classNames3 = {}, _classNames3[prefix + 'overlay-inner'] = true, _classNames3[prefix + 'dialog-wrapper'] = true, _classNames3[prefix + 'dialog-centered'] = centered, _classNames3));
351
352 var getVisibleOverlayFromChild = function getVisibleOverlayFromChild(id, node) {
353 if (node) {
354 childIDMap.current.set(id, node);
355 } else {
356 childIDMap.current.delete(id);
357 }
358 // 让父级也感知
359 setVisibleOverlayToParent(id, node);
360 };
361
362 return React.createElement(
363 OverlayContext.Provider,
364 {
365 value: _extends({}, otherContext, {
366 setVisibleOverlayToParent: getVisibleOverlayFromChild
367 })
368 },
369 ReactDOM.createPortal(React.createElement(
370 'div',
371 { className: wrapperCls, style: wrapperStyle, ref: wrapperRef },
372 hasMask ? React.createElement(
373 Animate.OverlayAnimate,
374 {
375 visible: visible,
376 animation: animation ? { in: 'fadeIn', out: 'fadeOut' } : false,
377 timeout: timeout,
378 unmountOnExit: true
379 },
380 React.createElement('div', { className: prefix + 'overlay-backdrop' })
381 ) : null,
382 React.createElement(
383 'div',
384 { className: innerWrapperCls, onClick: handleMaskClick },
385 centered ? inner : React.createElement(
386 'div',
387 { style: topStyle, className: prefix + 'dialog-inner-wrapper' },
388 inner
389 )
390 )
391 ), container)
392 );
393};
394
395export default Dialog;
\No newline at end of file