import classnames from 'classnames'; import { cloneVNode, withDirectives, Teleport, nextTick } from 'vue'; import antRef from '../../_util/ant-ref'; import BaseMixin from '../../_util/BaseMixin'; import { initDefaultProps, getSlot } from '../../_util/props-util'; import getScrollBarSize from '../../_util/getScrollBarSize'; import { IDrawerProps } from './IDrawerPropTypes'; import KeyCode from '../../_util/KeyCode'; import { dataToArray, transitionEnd, transitionStr, addEventListener, removeEventListener, transformArguments, isNumeric, } from './utils'; function noop() {} const currentDrawer = {}; const windowIsUndefined = !( typeof window !== 'undefined' && window.document && window.document.createElement ); const Drawer = { name: 'Drawer', mixins: [BaseMixin], inheritAttrs: false, directives: { 'ant-ref': antRef }, props: initDefaultProps(IDrawerProps, { prefixCls: 'drawer', placement: 'left', getContainer: 'body', level: 'all', duration: '.3s', ease: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)', firstEnter: false, // 记录首次进入. showMask: true, handler: true, maskStyle: {}, wrapperClassName: '', className: '', }), data() { this.levelDom = []; this.contentDom = null; this.maskDom = null; this.handlerdom = null; this.mousePos = null; this.sFirstEnter = this.firstEnter; this.timeout = null; this.children = null; this.drawerId = Number( (Date.now() + Math.random()).toString().replace('.', Math.round(Math.random() * 9)), ).toString(16); const open = this.open !== undefined ? this.open : !!this.defaultOpen; currentDrawer[this.drawerId] = open; this.orignalOpen = this.open; this.preProps = { ...this.$props }; return { sOpen: open, isOpenChange: undefined, passive: undefined, container: undefined, }; }, mounted() { nextTick(() => { if (!windowIsUndefined) { let passiveSupported = false; window.addEventListener( 'test', null, Object.defineProperty({}, 'passive', { get: () => { passiveSupported = true; return null; }, }), ); this.passive = passiveSupported ? { passive: false } : false; } const open = this.getOpen(); if (this.handler || open || this.sFirstEnter) { this.getDefault(this.$props); if (open) { this.isOpenChange = true; } this.$forceUpdate(); } }); }, watch: { open(val) { if (val !== undefined && val !== this.preProps.open) { this.isOpenChange = true; // 没渲染 dom 时,获取默认数据; if (!this.container) { this.getDefault(this.$props); } this.setState({ sOpen: open, }); } this.preProps.open = val; }, placement(val) { if (val !== this.preProps.placement) { // test 的 bug, 有动画过场,删除 dom this.contentDom = null; } this.preProps.placement = val; }, level(val) { if (this.preProps.level !== val) { this.getParentAndLevelDom(this.$props); } this.preProps.level = val; }, }, updated() { nextTick(() => { // dom 没渲染时,重走一遍。 if (!this.sFirstEnter && this.container) { this.$forceUpdate(); this.sFirstEnter = true; } }); }, beforeUnmount() { delete currentDrawer[this.drawerId]; delete this.isOpenChange; if (this.container) { if (this.sOpen) { this.setLevelDomTransform(false, true); } document.body.style.overflow = ''; } this.sFirstEnter = false; clearTimeout(this.timeout); }, methods: { onKeyDown(e) { if (e.keyCode === KeyCode.ESC) { e.stopPropagation(); this.$emit('close', e); } }, onMaskTouchEnd(e) { this.$emit('close', e); this.onTouchEnd(e, true); }, onIconTouchEnd(e) { this.$emit('handleClick', e); this.onTouchEnd(e); }, onTouchEnd(e, close) { if (this.open !== undefined) { return; } const open = close || this.sOpen; this.isOpenChange = true; this.setState({ sOpen: !open, }); }, onWrapperTransitionEnd(e) { if (e.target === this.contentWrapper && e.propertyName.match(/transform$/)) { const open = this.getOpen(); this.dom.style.transition = ''; if (!open && this.getCurrentDrawerSome()) { document.body.style.overflowX = ''; if (this.maskDom) { this.maskDom.style.left = ''; this.maskDom.style.width = ''; } } if (this.afterVisibleChange) { this.afterVisibleChange(!!open); } } }, getDefault(props) { this.getParentAndLevelDom(props); if (props.getContainer || props.parent) { this.container = this.defaultGetContainer(); } }, getCurrentDrawerSome() { return !Object.keys(currentDrawer).some(key => currentDrawer[key]); }, getSelfContainer() { return this.container; }, getParentAndLevelDom(props) { if (windowIsUndefined) { return; } const { level, getContainer } = props; this.levelDom = []; if (getContainer) { if (typeof getContainer === 'string') { const dom = document.querySelectorAll(getContainer)[0]; this.parent = dom; } if (typeof getContainer === 'function') { this.parent = getContainer(); } if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) { this.parent = getContainer; } } if (!getContainer && this.container) { this.parent = this.container.parentNode; } if (level === 'all') { const children = Array.prototype.slice.call(this.parent.children); children.forEach(child => { if ( child.nodeName !== 'SCRIPT' && child.nodeName !== 'STYLE' && child.nodeName !== 'LINK' && child !== this.container ) { this.levelDom.push(child); } }); } else if (level) { dataToArray(level).forEach(key => { document.querySelectorAll(key).forEach(item => { this.levelDom.push(item); }); }); } }, setLevelDomTransform(open, openTransition, placementName, value) { const { placement, levelMove, duration, ease, getContainer } = this.$props; if (!windowIsUndefined) { this.levelDom.forEach(dom => { if (this.isOpenChange || openTransition) { /* eslint no-param-reassign: "error" */ dom.style.transition = `transform ${duration} ${ease}`; addEventListener(dom, transitionEnd, this.trnasitionEnd); let levelValue = open ? value : 0; if (levelMove) { const $levelMove = transformArguments(levelMove, { target: dom, open }); levelValue = open ? $levelMove[0] : $levelMove[1] || 0; } const $value = typeof levelValue === 'number' ? `${levelValue}px` : levelValue; const placementPos = placement === 'left' || placement === 'top' ? $value : `-${$value}`; dom.style.transform = levelValue ? `${placementName}(${placementPos})` : ''; dom.style.msTransform = levelValue ? `${placementName}(${placementPos})` : ''; } }); // 处理 body 滚动 if (getContainer === 'body') { const eventArray = ['touchstart']; const domArray = [document.body, this.maskDom, this.handlerdom, this.contentDom]; const right = document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) && window.innerWidth > document.body.offsetWidth ? getScrollBarSize(1) : 0; let widthTransition = `width ${duration} ${ease}`; const trannsformTransition = `transform ${duration} ${ease}`; if (open && document.body.style.overflow !== 'hidden') { document.body.style.overflow = 'hidden'; if (right) { document.body.style.position = 'relative'; document.body.style.width = `calc(100% - ${right}px)`; this.dom.style.transition = 'none'; switch (placement) { case 'right': this.dom.style.transform = `translateX(-${right}px)`; this.dom.style.msTransform = `translateX(-${right}px)`; break; case 'top': case 'bottom': this.dom.style.width = `calc(100% - ${right}px)`; this.dom.style.transform = 'translateZ(0)'; break; default: break; } clearTimeout(this.timeout); this.timeout = setTimeout(() => { this.dom.style.transition = `${trannsformTransition},${widthTransition}`; this.dom.style.width = ''; this.dom.style.transform = ''; this.dom.style.msTransform = ''; }); } // 手机禁滚 domArray.forEach((item, i) => { if (!item) { return; } addEventListener( item, eventArray[i] || 'touchmove', i ? this.removeMoveHandler : this.removeStartHandler, this.passive, ); }); } else if (this.getCurrentDrawerSome()) { document.body.style.overflow = ''; if ((this.isOpenChange || openTransition) && right) { document.body.style.position = ''; document.body.style.width = ''; if (transitionStr) { document.body.style.overflowX = 'hidden'; } this.dom.style.transition = 'none'; let heightTransition; switch (placement) { case 'right': { this.dom.style.transform = `translateX(${right}px)`; this.dom.style.msTransform = `translateX(${right}px)`; this.dom.style.width = '100%'; widthTransition = `width 0s ${ease} ${duration}`; if (this.maskDom) { this.maskDom.style.left = `-${right}px`; this.maskDom.style.width = `calc(100% + ${right}px)`; } break; } case 'top': case 'bottom': { this.dom.style.width = `calc(100% + ${right}px)`; this.dom.style.height = '100%'; this.dom.style.transform = 'translateZ(0)'; heightTransition = `height 0s ${ease} ${duration}`; break; } default: break; } clearTimeout(this.timeout); this.timeout = setTimeout(() => { this.dom.style.transition = `${trannsformTransition},${ heightTransition ? `${heightTransition},` : '' }${widthTransition}`; this.dom.style.transform = ''; this.dom.style.msTransform = ''; this.dom.style.width = ''; this.dom.style.height = ''; }); } domArray.forEach((item, i) => { if (!item) { return; } removeEventListener( item, eventArray[i] || 'touchmove', i ? this.removeMoveHandler : this.removeStartHandler, this.passive, ); }); } } } const { onChange } = this.$attrs; if (onChange && this.isOpenChange && this.sFirstEnter) { onChange(open); this.isOpenChange = false; } }, getChildToRender(open) { const { className, prefixCls, placement, handler, showMask, maskStyle, width, height, wrapStyle, keyboard, maskClosable, } = this.$props; const { class: cls, style } = this.$attrs; const children = getSlot(this); const wrapperClassname = classnames(prefixCls, { [`${prefixCls}-${placement}`]: true, [`${prefixCls}-open`]: open, [className]: !!className, 'no-mask': !showMask, [cls]: true, }); const isOpenChange = this.isOpenChange; const isHorizontal = placement === 'left' || placement === 'right'; const placementName = `translate${isHorizontal ? 'X' : 'Y'}`; // 百分比与像素动画不同步,第一次打用后全用像素动画。 // const defaultValue = !this.contentDom || !level ? '100%' : `${value}px`; const placementPos = placement === 'left' || placement === 'top' ? '-100%' : '100%'; const transform = open ? '' : `${placementName}(${placementPos})`; if (isOpenChange === undefined || isOpenChange) { const contentValue = this.contentDom ? this.contentDom.getBoundingClientRect()[isHorizontal ? 'width' : 'height'] : 0; const value = (isHorizontal ? width : height) || contentValue; this.setLevelDomTransform(open, false, placementName, value); } let handlerChildren; if (handler !== false) { const handlerDefalut = (