import classnames from 'classnames' import Vue from 'vue' import ref from 'vue-ref' import BaseMixin from '../../_util/BaseMixin' import { initDefaultProps, getEvents, getClass } from '../../_util/props-util' import { cloneElement } from '../../_util/vnode' import ContainerRender from '../../_util/ContainerRender' import getScrollBarSize from '../../_util/getScrollBarSize' import drawerProps from './drawerProps' import { dataToArray, transitionEnd, transitionStr, addEventListener, removeEventListener, transformArguments, isNumeric, } from './utils' function noop () {} const currentDrawer = {} const windowIsUndefined = !( typeof window !== 'undefined' && window.document && window.document.createElement ) Vue.use(ref, { name: 'ant-ref' }) const Drawer = { mixins: [BaseMixin], props: initDefaultProps(drawerProps, { 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: '', }), 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, } }, mounted () { this.$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 () { this.$nextTick(() => { // dom 没渲染时,重走一遍。 if (!this.sFirstEnter && this.container) { this.$forceUpdate() this.sFirstEnter = true } }) }, beforeDestroy () { delete currentDrawer[this.drawerId] delete this.isOpenChange if (this.container) { if (this.sOpen) { this.setLevelDomTransform(false, true) } document.body.style.overflow = '' // 拦不住。。直接删除; if (this.getSelfContainer) { this.container.parentNode.removeChild(this.container) } } this.sFirstEnter = false clearTimeout(this.timeout) // 需要 didmount 后也会渲染,直接 unmount 将不会渲染,加上判断. if (this.renderComponent) { this.renderComponent({ afterClose: this.removeContainer, onClose () { }, visible: false, }) } }, methods: { onMaskTouchEnd (e) { this.$emit('maskClick', 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) { this.dom.style.transition = '' if (!this.sOpen && this.getCurrentDrawerSome()) { document.body.style.overflowX = '' if (this.maskDom) { this.maskDom.style.left = '' this.maskDom.style.width = '' } } } }, 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 { change } = this.$listeners if (change && this.isOpenChange && this.sFirstEnter) { change(open) this.isOpenChange = false } }, getChildToRender (open) { const { prefixCls, placement, handler, showMask, maskStyle, width, height, } = this.$props const children = this.$slots.default const wrapperClassname = classnames(prefixCls, { [`${prefixCls}-${placement}`]: true, [`${prefixCls}-open`]: open, ...getClass(this), }) 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 = (