551 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Vue
		
	
	
			
		
		
	
	
			551 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Vue
		
	
	
| <script>
 | |
| import Vue from 'vue'
 | |
| import PropTypes from '../_util/vue-types'
 | |
| import contains from '../_util/Dom/contains'
 | |
| import { hasProp, getComponentFromProp } from '../_util/props-util'
 | |
| import { requestAnimationTimeout, cancelAnimationTimeout } from '../_util/requestAnimationTimeout'
 | |
| import addEventListener from '../_util/Dom/addEventListener'
 | |
| import warning from '../_util/warning'
 | |
| import Popup from './Popup'
 | |
| import { getAlignFromPlacement, getPopupClassNameFromAlign, noop } from './utils'
 | |
| import BaseMixin from '../_util/BaseMixin'
 | |
| import { cloneElement, filterEmpty, getEvents, cloneVNode } from '../_util/vnode'
 | |
| 
 | |
| function returnEmptyString () {
 | |
|   return ''
 | |
| }
 | |
| 
 | |
| function returnDocument () {
 | |
|   return window.document
 | |
| }
 | |
| const ALL_HANDLERS = ['click', 'mousedown', 'touchstart', 'mouseenter',
 | |
|   'mouseleave', 'focus', 'blur', 'contextmenu']
 | |
| 
 | |
| export default {
 | |
|   name: 'Trigger',
 | |
|   props: {
 | |
|     action: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]).def([]),
 | |
|     showAction: PropTypes.any.def([]),
 | |
|     hideAction: PropTypes.any.def([]),
 | |
|     getPopupClassNameFromAlign: PropTypes.any.def(returnEmptyString),
 | |
|     // onPopupVisibleChange: PropTypes.func.def(noop),
 | |
|     afterPopupVisibleChange: PropTypes.func.def(noop),
 | |
|     popup: PropTypes.any,
 | |
|     popupStyle: PropTypes.object.def({}),
 | |
|     prefixCls: PropTypes.string.def('rc-trigger-popup'),
 | |
|     popupClassName: PropTypes.string.def(''),
 | |
|     popupPlacement: PropTypes.string,
 | |
|     builtinPlacements: PropTypes.object,
 | |
|     popupTransitionName: PropTypes.oneOfType([
 | |
|       PropTypes.string,
 | |
|       PropTypes.object,
 | |
|     ]),
 | |
|     popupAnimation: PropTypes.any,
 | |
|     mouseEnterDelay: PropTypes.number.def(0),
 | |
|     mouseLeaveDelay: PropTypes.number.def(0.1),
 | |
|     zIndex: PropTypes.number,
 | |
|     focusDelay: PropTypes.number.def(0),
 | |
|     blurDelay: PropTypes.number.def(0.15),
 | |
|     getPopupContainer: PropTypes.func,
 | |
|     getDocument: PropTypes.func.def(returnDocument),
 | |
|     forceRender: PropTypes.bool,
 | |
|     destroyPopupOnHide: PropTypes.bool.def(false),
 | |
|     mask: PropTypes.bool.def(false),
 | |
|     maskClosable: PropTypes.bool.def(true),
 | |
|     // onPopupAlign: PropTypes.func.def(noop),
 | |
|     popupAlign: PropTypes.object.def({}),
 | |
|     popupVisible: PropTypes.bool,
 | |
|     defaultPopupVisible: PropTypes.bool.def(false),
 | |
|     maskTransitionName: PropTypes.oneOfType([
 | |
|       PropTypes.string,
 | |
|       PropTypes.object,
 | |
|     ]),
 | |
|     maskAnimation: PropTypes.string,
 | |
|   },
 | |
| 
 | |
|   mixins: [BaseMixin],
 | |
|   data () {
 | |
|     const props = this.$props
 | |
|     let popupVisible
 | |
|     if (hasProp(this, 'popupVisible')) {
 | |
|       popupVisible = !!props.popupVisible
 | |
|     } else {
 | |
|       popupVisible = !!props.defaultPopupVisible
 | |
|     }
 | |
|     return {
 | |
|       sPopupVisible: popupVisible,
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   beforeCreate () {
 | |
|     ALL_HANDLERS.forEach((h) => {
 | |
|       this[`fire${h}`] = (e) => {
 | |
|         this.fireEvents(h, e)
 | |
|       }
 | |
|     })
 | |
|   },
 | |
| 
 | |
|   mounted () {
 | |
|     this.$nextTick(() => {
 | |
|       this.updatedCal()
 | |
|     })
 | |
|   },
 | |
|   watch: {
 | |
|     popupVisible (val) {
 | |
|       if (val !== undefined) {
 | |
|         this.sPopupVisible = val
 | |
|       }
 | |
|     },
 | |
|     sPopupVisible (val) {
 | |
|       this.$nextTick(() => {
 | |
|         this.afterPopupVisibleChange(val)
 | |
|       })
 | |
|     },
 | |
|   },
 | |
| 
 | |
|   updated () {
 | |
|     this.$nextTick(() => {
 | |
|       this.updatedCal()
 | |
|     })
 | |
|   },
 | |
| 
 | |
|   beforeDestroy () {
 | |
|     this.clearDelayTimer()
 | |
|     this.clearOutsideHandler()
 | |
|     if (this._component) {
 | |
|       this._component.$destroy()
 | |
|       this._component = null
 | |
|       this.popupContainer.remove()
 | |
|     }
 | |
|   },
 | |
|   methods: {
 | |
|     updatedCal () {
 | |
|       const props = this.$props
 | |
|       const state = this.$data
 | |
| 
 | |
|       // We must listen to `mousedown` or `touchstart`, edge case:
 | |
|       // https://github.com/ant-design/ant-design/issues/5804
 | |
|       // https://github.com/react-component/calendar/issues/250
 | |
|       // https://github.com/react-component/trigger/issues/50
 | |
|       if (state.sPopupVisible) {
 | |
|         let currentDocument
 | |
|         if (!this.clickOutsideHandler && (this.isClickToHide() || this.isContextmenuToShow())) {
 | |
|           currentDocument = props.getDocument()
 | |
|           this.clickOutsideHandler = addEventListener(currentDocument,
 | |
|             'mousedown', this.onDocumentClick)
 | |
|         }
 | |
|         // always hide on mobile
 | |
|         if (!this.touchOutsideHandler) {
 | |
|           currentDocument = currentDocument || props.getDocument()
 | |
|           this.touchOutsideHandler = addEventListener(currentDocument,
 | |
|             'touchstart', this.onDocumentClick)
 | |
|         }
 | |
|         // close popup when trigger type contains 'onContextmenu' and document is scrolling.
 | |
|         if (!this.contextmenuOutsideHandler1 && this.isContextmenuToShow()) {
 | |
|           currentDocument = currentDocument || props.getDocument()
 | |
|           this.contextmenuOutsideHandler1 = addEventListener(currentDocument,
 | |
|             'scroll', this.onContextmenuClose)
 | |
|         }
 | |
|         // close popup when trigger type contains 'onContextmenu' and window is blur.
 | |
|         if (!this.contextmenuOutsideHandler2 && this.isContextmenuToShow()) {
 | |
|           this.contextmenuOutsideHandler2 = addEventListener(window,
 | |
|             'blur', this.onContextmenuClose)
 | |
|         }
 | |
|       } else {
 | |
|         this.clearOutsideHandler()
 | |
|       }
 | |
|     },
 | |
|     onMouseenter (e) {
 | |
|       this.fireEvents('mouseenter', e)
 | |
|       this.delaySetPopupVisible(true, this.$props.mouseEnterDelay)
 | |
|     },
 | |
| 
 | |
|     onMouseleave (e) {
 | |
|       this.fireEvents('mouseleave', e)
 | |
|       this.delaySetPopupVisible(false, this.$props.mouseLeaveDelay)
 | |
|     },
 | |
| 
 | |
|     onPopupMouseenter () {
 | |
|       this.clearDelayTimer()
 | |
|     },
 | |
| 
 | |
|     onPopupMouseleave (e) {
 | |
|       if (e.relatedTarget && !e.relatedTarget.setTimeout &&
 | |
|       this._component &&
 | |
|       this._component.$refs.popup &&
 | |
|       this._component.$refs.popup.getPopupDomNode &&
 | |
|       contains(this._component.$refs.popup.getPopupDomNode(), e.relatedTarget)) {
 | |
|         return
 | |
|       }
 | |
|       this.delaySetPopupVisible(false, this.$props.mouseLeaveDelay)
 | |
|     },
 | |
| 
 | |
|     onFocus (e) {
 | |
|       this.fireEvents('focus', e)
 | |
|       // incase focusin and focusout
 | |
|       this.clearDelayTimer()
 | |
|       if (this.isFocusToShow()) {
 | |
|         this.focusTime = Date.now()
 | |
|         this.delaySetPopupVisible(true, this.$props.focusDelay)
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     onMousedown (e) {
 | |
|       this.fireEvents('mousedown', e)
 | |
|       this.preClickTime = Date.now()
 | |
|     },
 | |
| 
 | |
|     onTouchstart (e) {
 | |
|       this.fireEvents('touchstart', e)
 | |
|       this.preTouchTime = Date.now()
 | |
|     },
 | |
| 
 | |
|     onBlur (e) {
 | |
|       this.fireEvents('blur', e)
 | |
|       this.clearDelayTimer()
 | |
|       if (this.isBlurToHide()) {
 | |
|         this.delaySetPopupVisible(false, this.$props.blurDelay)
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     onContextmenu (e) {
 | |
|       e.preventDefault()
 | |
|       this.fireEvents('contextmenu', e)
 | |
|       this.setPopupVisible(true)
 | |
|     },
 | |
| 
 | |
|     onContextmenuClose () {
 | |
|       if (this.isContextmenuToShow()) {
 | |
|         this.close()
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     onClick (event) {
 | |
|       this.fireEvents('click', event)
 | |
|       // focus will trigger click
 | |
|       if (this.focusTime) {
 | |
|         let preTime
 | |
|         if (this.preClickTime && this.preTouchTime) {
 | |
|           preTime = Math.min(this.preClickTime, this.preTouchTime)
 | |
|         } else if (this.preClickTime) {
 | |
|           preTime = this.preClickTime
 | |
|         } else if (this.preTouchTime) {
 | |
|           preTime = this.preTouchTime
 | |
|         }
 | |
|         if (Math.abs(preTime - this.focusTime) < 20) {
 | |
|           return
 | |
|         }
 | |
|         this.focusTime = 0
 | |
|       }
 | |
|       this.preClickTime = 0
 | |
|       this.preTouchTime = 0
 | |
|       event.preventDefault && event.preventDefault()
 | |
|       if (event.domEvent) {
 | |
|         event.domEvent.preventDefault()
 | |
|       }
 | |
|       const nextVisible = !this.$data.sPopupVisible
 | |
|       if (this.isClickToHide() && !nextVisible || nextVisible && this.isClickToShow()) {
 | |
|         this.setPopupVisible(!this.$data.sPopupVisible)
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     onDocumentClick (event) {
 | |
|       if (this.$props.mask && !this.$props.maskClosable) {
 | |
|         return
 | |
|       }
 | |
|       const target = event.target
 | |
|       const root = this.$el
 | |
|       const popupNode = this.getPopupDomNode()
 | |
|       if (!contains(root, target) && !contains(popupNode, target)) {
 | |
|         this.close()
 | |
|       }
 | |
|     },
 | |
|     getPopupDomNode () {
 | |
|       if (this._component && this._component.$refs.popup && this._component.$refs.popup.getPopupDomNode) {
 | |
|         return this._component.$refs.popup.getPopupDomNode()
 | |
|       }
 | |
|       return null
 | |
|     },
 | |
| 
 | |
|     getRootDomNode () {
 | |
|       return this.$el
 | |
|       // return this.$el.children[0] || this.$el
 | |
|     },
 | |
| 
 | |
|     handleGetPopupClassFromAlign (align) {
 | |
|       const className = []
 | |
|       const props = this.$props
 | |
|       const { popupPlacement, builtinPlacements, prefixCls } = props
 | |
|       if (popupPlacement && builtinPlacements) {
 | |
|         className.push(getPopupClassNameFromAlign(builtinPlacements, prefixCls, align))
 | |
|       }
 | |
|       if (props.getPopupClassNameFromAlign) {
 | |
|         className.push(props.getPopupClassNameFromAlign(align))
 | |
|       }
 | |
|       return className.join(' ')
 | |
|     },
 | |
| 
 | |
|     getPopupAlign () {
 | |
|       const props = this.$props
 | |
|       const { popupPlacement, popupAlign, builtinPlacements } = props
 | |
|       if (popupPlacement && builtinPlacements) {
 | |
|         return getAlignFromPlacement(builtinPlacements, popupPlacement, popupAlign)
 | |
|       }
 | |
|       return popupAlign
 | |
|     },
 | |
|     renderComponent () {
 | |
|       const self = this
 | |
|       const mouseProps = {}
 | |
|       if (this.isMouseEnterToShow()) {
 | |
|         mouseProps.mouseenter = self.onPopupMouseenter
 | |
|       }
 | |
|       if (this.isMouseLeaveToHide()) {
 | |
|         mouseProps.mouseleave = self.onPopupMouseleave
 | |
|       }
 | |
|       const { prefixCls, destroyPopupOnHide, sPopupVisible,
 | |
|         popupStyle, popupClassName, action,
 | |
|         popupAnimation, handleGetPopupClassFromAlign, getRootDomNode,
 | |
|         mask, zIndex, popupTransitionName, getPopupAlign,
 | |
|         maskAnimation, maskTransitionName, getContainer } = self
 | |
|       const popupProps = {
 | |
|         prefixCls,
 | |
|         destroyPopupOnHide,
 | |
|         visible: sPopupVisible,
 | |
|         action,
 | |
|         align: getPopupAlign(),
 | |
|         animation: popupAnimation,
 | |
|         getClassNameFromAlign: handleGetPopupClassFromAlign,
 | |
|         getRootDomNode,
 | |
|         mask,
 | |
|         zIndex,
 | |
|         transitionName: popupTransitionName,
 | |
|         maskAnimation,
 | |
|         maskTransitionName,
 | |
|         getContainer,
 | |
|         popupClassName,
 | |
|         popupStyle,
 | |
|         popupEvents: {
 | |
|           align: self.$listeners.popupAlign || noop,
 | |
|           ...mouseProps,
 | |
|         },
 | |
|       }
 | |
|       if (!this._component) {
 | |
|         const div = document.createElement('div')
 | |
|         this.getContainer().appendChild(div)
 | |
|         this._component = new Vue({
 | |
|           data () {
 | |
|             return {
 | |
|               popupProps: { ...popupProps },
 | |
|             }
 | |
|           },
 | |
|           el: div,
 | |
|           render () {
 | |
|             const { popupStyle, popupEvents, ...otherProps } = this.popupProps
 | |
|             const p = {
 | |
|               props: otherProps,
 | |
|               on: popupEvents,
 | |
|               ref: 'popup',
 | |
|               style: popupStyle,
 | |
|             }
 | |
|             return (
 | |
|               <Popup
 | |
|                 {...p}
 | |
|               >
 | |
|                 {getComponentFromProp(self, 'popup')}
 | |
|               </Popup>
 | |
|             )
 | |
|           },
 | |
|         })
 | |
|       } else {
 | |
|         this._component.popupProps = popupProps
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     getContainer () {
 | |
|       const { $props: props } = this
 | |
|       const popupContainer = document.createElement('div')
 | |
|       // Make sure default popup container will never cause scrollbar appearing
 | |
|       // https://github.com/react-component/trigger/issues/41
 | |
|       popupContainer.style.position = 'absolute'
 | |
|       popupContainer.style.top = '0'
 | |
|       popupContainer.style.left = '0'
 | |
|       popupContainer.style.width = '100%'
 | |
|       const mountNode = props.getPopupContainer
 | |
|         ? props.getPopupContainer(this.$el) : props.getDocument().body
 | |
|       mountNode.appendChild(popupContainer)
 | |
|       this.popupContainer = popupContainer
 | |
|       return popupContainer
 | |
|     },
 | |
| 
 | |
|     setPopupVisible (sPopupVisible) {
 | |
|       this.clearDelayTimer()
 | |
|       if (this.$data.sPopupVisible !== sPopupVisible) {
 | |
|         if (!hasProp(this, 'popupVisible')) {
 | |
|           this.setState({
 | |
|             sPopupVisible,
 | |
|           })
 | |
|         }
 | |
|         this.$listeners.popupVisibleChange && this.$listeners.popupVisibleChange(sPopupVisible)
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     delaySetPopupVisible (visible, delayS) {
 | |
|       const delay = delayS * 1000
 | |
|       this.clearDelayTimer()
 | |
|       if (delay) {
 | |
|         this.delayTimer = requestAnimationTimeout(() => {
 | |
|           this.setPopupVisible(visible)
 | |
|           this.clearDelayTimer()
 | |
|         }, delay)
 | |
|       } else {
 | |
|         this.setPopupVisible(visible)
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     clearDelayTimer () {
 | |
|       if (this.delayTimer) {
 | |
|         cancelAnimationTimeout(this.delayTimer)
 | |
|         this.delayTimer = null
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     clearOutsideHandler () {
 | |
|       if (this.clickOutsideHandler) {
 | |
|         this.clickOutsideHandler.remove()
 | |
|         this.clickOutsideHandler = null
 | |
|       }
 | |
| 
 | |
|       if (this.contextmenuOutsideHandler1) {
 | |
|         this.contextmenuOutsideHandler1.remove()
 | |
|         this.contextmenuOutsideHandler1 = null
 | |
|       }
 | |
| 
 | |
|       if (this.contextmenuOutsideHandler2) {
 | |
|         this.contextmenuOutsideHandler2.remove()
 | |
|         this.contextmenuOutsideHandler2 = null
 | |
|       }
 | |
| 
 | |
|       if (this.touchOutsideHandler) {
 | |
|         this.touchOutsideHandler.remove()
 | |
|         this.touchOutsideHandler = null
 | |
|       }
 | |
|     },
 | |
| 
 | |
|     createTwoChains (event) {
 | |
|       let fn = () => {
 | |
|       }
 | |
|       const events = this.$listeners
 | |
|       if (this.childOriginEvents[event] && events[event]) {
 | |
|         return this[`fire${event}`]
 | |
|       }
 | |
|       fn = this.childOriginEvents[event] || events[event] || fn
 | |
|       return fn
 | |
|     },
 | |
| 
 | |
|     isClickToShow () {
 | |
|       const { action, showAction } = this.$props
 | |
|       return action.indexOf('click') !== -1 || showAction.indexOf('click') !== -1
 | |
|     },
 | |
| 
 | |
|     isContextmenuToShow () {
 | |
|       const { action, showAction } = this.$props
 | |
|       return action.indexOf('contextmenu') !== -1 || showAction.indexOf('contextmenu') !== -1
 | |
|     },
 | |
| 
 | |
|     isClickToHide () {
 | |
|       const { action, hideAction } = this.$props
 | |
|       return action.indexOf('click') !== -1 || hideAction.indexOf('click') !== -1
 | |
|     },
 | |
| 
 | |
|     isMouseEnterToShow () {
 | |
|       const { action, showAction } = this.$props
 | |
|       return action.indexOf('hover') !== -1 || showAction.indexOf('mouseenter') !== -1
 | |
|     },
 | |
| 
 | |
|     isMouseLeaveToHide () {
 | |
|       const { action, hideAction } = this.$props
 | |
|       return action.indexOf('hover') !== -1 || hideAction.indexOf('mouseleave') !== -1
 | |
|     },
 | |
| 
 | |
|     isFocusToShow () {
 | |
|       const { action, showAction } = this.$props
 | |
|       return action.indexOf('focus') !== -1 || showAction.indexOf('focus') !== -1
 | |
|     },
 | |
| 
 | |
|     isBlurToHide () {
 | |
|       const { action, hideAction } = this.$props
 | |
|       return action.indexOf('focus') !== -1 || hideAction.indexOf('blur') !== -1
 | |
|     },
 | |
|     forcePopupAlign () {
 | |
|       if (this.$data.sPopupVisible && this._component && this._component.$refs.popup && this._component.$refs.popup.$refs.alignInstance) {
 | |
|         this._component.$refs.popup.$refs.alignInstance.forceAlign()
 | |
|       }
 | |
|     },
 | |
|     fireEvents (type, e) {
 | |
|       if (this.childOriginEvents[type]) {
 | |
|         this.childOriginEvents[type](e)
 | |
|       }
 | |
|       this.__emit(type, e)
 | |
|     },
 | |
| 
 | |
|     close () {
 | |
|       this.setPopupVisible(false)
 | |
|     },
 | |
|   },
 | |
|   render (h) {
 | |
|     const children = filterEmpty(this.$slots.default)
 | |
|     if (children.length > 1) {
 | |
|       warning(false, 'Trigger $slots.default.length > 1, just support only one default', true)
 | |
|     }
 | |
|     const child = cloneVNode(children[0])
 | |
|     this.childOriginEvents = getEvents(children[0])
 | |
|     const newChildProps = {
 | |
|       props: {},
 | |
|       on: {},
 | |
|       key: 'trigger',
 | |
|     }
 | |
| 
 | |
|     if (this.isContextmenuToShow()) {
 | |
|       newChildProps.on.contextmenu = this.onContextmenu
 | |
|     } else {
 | |
|       newChildProps.on.contextmenu = this.createTwoChains('contextmenu')
 | |
|     }
 | |
| 
 | |
|     if (this.isClickToHide() || this.isClickToShow()) {
 | |
|       newChildProps.on.click = this.onClick
 | |
|       newChildProps.on.mousedown = this.onMousedown
 | |
|       newChildProps.on.touchstart = this.onTouchstart
 | |
|     } else {
 | |
|       newChildProps.on.click = this.createTwoChains('click')
 | |
|       newChildProps.on.mousedown = this.createTwoChains('mousedown')
 | |
|       newChildProps.on.touchstart = this.createTwoChains('onTouchstart')
 | |
|     }
 | |
|     if (this.isMouseEnterToShow()) {
 | |
|       newChildProps.on.mouseenter = this.onMouseenter
 | |
|     } else {
 | |
|       newChildProps.on.mouseenter = this.createTwoChains('mouseenter')
 | |
|     }
 | |
|     if (this.isMouseLeaveToHide()) {
 | |
|       newChildProps.on.mouseleave = this.onMouseleave
 | |
|     } else {
 | |
|       newChildProps.on.mouseleave = this.createTwoChains('mouseleave')
 | |
|     }
 | |
| 
 | |
|     if (this.isFocusToShow() || this.isBlurToHide()) {
 | |
|       newChildProps.on.focus = this.onFocus
 | |
|       newChildProps.on.blur = this.onBlur
 | |
|     } else {
 | |
|       newChildProps.on.focus = this.createTwoChains('focus')
 | |
|       newChildProps.on.blur = this.createTwoChains('blur')
 | |
|     }
 | |
|     const { sPopupVisible, forceRender } = this
 | |
|     if (sPopupVisible || forceRender || this._component) {
 | |
|       this.renderComponent(h)
 | |
|     }
 | |
|     const trigger = cloneElement(child, newChildProps)
 | |
|     return trigger
 | |
|   },
 | |
| }
 | |
| 
 | |
| </script>
 |