import { defineComponent, provide } from 'vue'; import { initDefaultProps, getSlot, findDOMNode } from '../_util/props-util'; import KeyCode from '../_util/KeyCode'; import contains from '../vc-util/Dom/contains'; import LazyRenderBox from './LazyRenderBox'; import BaseMixin from '../_util/BaseMixin'; import { getTransitionProps, Transition } from '../_util/transition'; import switchScrollingEffect from '../_util/switchScrollingEffect'; import getDialogPropTypes from './IDialogPropTypes'; import warning from '../_util/warning'; const IDialogPropTypes = getDialogPropTypes(); let uuid = 0; function noop() {} function getScroll(w, top) { let ret = w[`page${top ? 'Y' : 'X'}Offset`]; const method = `scroll${top ? 'Top' : 'Left'}`; if (typeof ret !== 'number') { const d = w.document; ret = d.documentElement[method]; if (typeof ret !== 'number') { ret = d.body[method]; } } return ret; } function setTransformOrigin(node, value) { const style = node.style; ['Webkit', 'Moz', 'Ms', 'ms'].forEach(prefix => { style[`${prefix}TransformOrigin`] = value; }); style[`transformOrigin`] = value; } function offset(el) { const rect = el.getBoundingClientRect(); const pos = { left: rect.left, top: rect.top, }; const doc = el.ownerDocument; const w = doc.defaultView || doc.parentWindow; pos.left += getScroll(w); pos.top += getScroll(w, true); return pos; } let cacheOverflow = {}; export default defineComponent({ name: 'VcDialog', mixins: [BaseMixin], inheritAttrs: false, props: initDefaultProps(IDialogPropTypes, { mask: true, visible: false, keyboard: true, closable: true, maskClosable: true, destroyOnClose: false, prefixCls: 'rc-dialog', getOpenCount: () => null, focusTriggerAfterClose: true, }), data() { warning(!this.dialogClass, 'Modal', 'dialogClass is deprecated, please use class instead.'); warning(!this.dialogStyle, 'Modal', 'dialogStyle is deprecated, please use style instead.'); return { inTransition: false, titleId: `rcDialogTitle${uuid++}`, dialogMouseDown: undefined, }; }, watch: { visible(val) { this.$nextTick(() => { this.updatedCallback(!val); }); }, }, created() { provide('dialogContext', this); }, mounted() { this.$nextTick(() => { this.updatedCallback(false); // if forceRender is true, set element style display to be none; if ((this.forceRender || (this.getContainer === false && !this.visible)) && this.$refs.wrap) { this.$refs.wrap.style.display = 'none'; } }); }, beforeUnmount() { const { visible, getOpenCount } = this; if ((visible || this.inTransition) && !getOpenCount()) { this.switchScrollingEffect(); } clearTimeout(this.timeoutId); }, methods: { // 对外暴露的 api 不要更改名称或删除 getDialogWrap() { return this.$refs.wrap; }, updatedCallback(visible) { const mousePosition = this.mousePosition; const { mask, focusTriggerAfterClose } = this; if (this.visible) { // first show if (!visible) { this.openTime = Date.now(); // this.lastOutSideFocusNode = document.activeElement this.switchScrollingEffect(); // this.$refs.wrap.focus() this.tryFocus(); const dialogNode = findDOMNode(this.$refs.dialog); if (mousePosition) { const elOffset = offset(dialogNode); setTransformOrigin( dialogNode, `${mousePosition.x - elOffset.left}px ${mousePosition.y - elOffset.top}px`, ); } else { setTransformOrigin(dialogNode, ''); } } } else if (visible) { this.inTransition = true; if (mask && this.lastOutSideFocusNode && focusTriggerAfterClose) { try { this.lastOutSideFocusNode.focus(); } catch (e) { this.lastOutSideFocusNode = null; } this.lastOutSideFocusNode = null; } } }, tryFocus() { if (!contains(this.$refs.wrap, document.activeElement)) { this.lastOutSideFocusNode = document.activeElement; this.$refs.sentinelStart.focus(); } }, onAnimateLeave() { const { afterClose } = this; // need demo? // https://github.com/react-component/dialog/pull/28 if (this.$refs.wrap) { this.$refs.wrap.style.display = 'none'; } this.inTransition = false; this.switchScrollingEffect(); if (afterClose) { afterClose(); } }, onDialogMouseDown() { this.dialogMouseDown = true; }, onMaskMouseUp() { if (this.dialogMouseDown) { this.timeoutId = setTimeout(() => { this.dialogMouseDown = false; }, 0); } }, onMaskClick(e) { // android trigger click on open (fastclick??) if (Date.now() - this.openTime < 300) { return; } if (e.target === e.currentTarget && !this.dialogMouseDown) { this.close(e); } }, onKeydown(e) { const props = this.$props; if (props.keyboard && e.keyCode === KeyCode.ESC) { e.stopPropagation(); this.close(e); return; } // keep focus inside dialog if (props.visible) { if (e.keyCode === KeyCode.TAB) { const activeElement = document.activeElement; const sentinelStart = this.$refs.sentinelStart; if (e.shiftKey) { if (activeElement === sentinelStart) { this.$refs.sentinelEnd.focus(); } } else if (activeElement === this.$refs.sentinelEnd) { sentinelStart.focus(); } } } }, getDialogElement() { const { closable, prefixCls, width, height, title, footer: tempFooter, bodyStyle, visible, bodyProps, forceRender, closeIcon, dialogStyle = {}, dialogClass = '', } = this; const dest = { ...dialogStyle }; if (width !== undefined) { dest.width = typeof width === 'number' ? `${width}px` : width; } if (height !== undefined) { dest.height = typeof height === 'number' ? `${height}px` : height; } let footer; if (tempFooter) { footer = (
{tempFooter}
); } let header; if (title) { header = (
{title}
); } let closer; if (closable) { closer = ( ); } const { style: stl, class: className } = this.$attrs; const style = { ...stl, ...dest }; const sentinelStyle = { width: 0, height: 0, overflow: 'hidden' }; const cls = [prefixCls, className, dialogClass]; const transitionName = this.getTransitionName(); const dialogElement = (