pull/9/head
tangjinzhou 2017-12-25 18:08:36 +08:00
parent 78d3332bb3
commit 2e07f98d40
14 changed files with 805 additions and 153 deletions

View File

@ -1,3 +1,4 @@
import Vue from 'vue'
function defaultGetContainer () {
const container = document.createElement('div')
document.body.appendChild(container)
@ -19,7 +20,7 @@ export default function getContainerRenderMixin (config) {
mixin = {
...mixin,
mounted () {
this.renderComponent()
// this.renderComponent()
},
}
}
@ -28,7 +29,7 @@ export default function getContainerRenderMixin (config) {
mixin = {
...mixin,
beforeDestroy () {
this.removeContainer()
// this.removeContainer()
},
}
}
@ -44,21 +45,29 @@ export default function getContainerRenderMixin (config) {
},
renderComponent (componentArg) {
if (
!isVisible || isVisible(this) ||
!isVisible || this._component || isVisible(this) ||
(isForceRender && isForceRender(this))
) {
if (!this._container) {
this._container = getContainer(this)
}
this._container.appendChild(this.$el)
let component
if (this.getComponent) {
component = this.getComponent(componentArg)
} else {
component = getComponent(this, componentArg)
}
this._component = component
const vmC = document.createElement('div')
this._container.appendChild(vmC)
new Vue({
el: vmC,
render () {
return component
},
})
}
let component
if (this.getComponent) {
component = this.getComponent(componentArg)
} else {
component = getComponent(this, componentArg)
}
this._component = component
},
},
}

View File

@ -30,15 +30,17 @@ export function cloneVNodes (vnodes, deep) {
}
export function cloneElement (node, nodeProps) {
const { props, key } = nodeProps
const { props, key, ref } = nodeProps
if (node.componentOptions) {
Object.assign(node.componentOptions.propsData, props)
}
if (node.data) {
const data = node.data
const { style = data.style, class: cls = data.class, attrs = data.attrs } = nodeProps
Object.assign(node.data, { style, attrs, class: cls })
}
const data = node.data || {}
const { style = data.style,
class: cls = data.class,
attrs = data.attrs,
on = data.on,
} = nodeProps
Object.assign(node.data, { style, attrs, class: cls, on })
if (key !== undefined) {
node.key = key
}

View File

@ -0,0 +1,258 @@
import isPlainObject from 'lodash.isplainobject'
import { noop, toType, getType, isFunction, validateType, isInteger, isArray, warn } from './utils'
const VuePropTypes = {
get any () {
return toType('any', {
type: null,
})
},
get func () {
return toType('function', {
type: Function,
}).def(currentDefaults.func)
},
get bool () {
return toType('boolean', {
type: Boolean,
}).def(currentDefaults.bool)
},
get string () {
return toType('string', {
type: String,
}).def(currentDefaults.string)
},
get number () {
return toType('number', {
type: Number,
}).def(currentDefaults.number)
},
get array () {
return toType('array', {
type: Array,
}).def(currentDefaults.array)
},
get object () {
return toType('object', {
type: Object,
}).def(currentDefaults.object)
},
get integer () {
return toType('integer', {
type: Number,
validator (value) {
return isInteger(value)
},
}).def(currentDefaults.integer)
},
get symbol () {
return toType('symbol', {
type: null,
validator (value) {
return typeof value === 'symbol'
},
})
},
custom (validatorFn, warnMsg = 'custom validation failed') {
if (typeof validatorFn !== 'function') {
throw new TypeError('[VueTypes error]: You must provide a function as argument')
}
return toType(validatorFn.name || '<<anonymous function>>', {
validator (...args) {
const valid = validatorFn(...args)
if (!valid) warn(`${this._vueTypes_name} - ${warnMsg}`)
return valid
},
})
},
oneOf (arr) {
if (!isArray(arr)) {
throw new TypeError('[VueTypes error]: You must provide an array as argument')
}
const msg = `oneOf - value should be one of "${arr.join('", "')}"`
const allowedTypes = arr.reduce((ret, v) => {
if (v !== null && v !== undefined) {
ret.indexOf(v.constructor) === -1 && ret.push(v.constructor)
}
return ret
}, [])
return toType('oneOf', {
type: allowedTypes.length > 0 ? allowedTypes : null,
validator (value) {
const valid = arr.indexOf(value) !== -1
if (!valid) warn(msg)
return valid
},
})
},
instanceOf (instanceConstructor) {
return toType('instanceOf', {
type: instanceConstructor,
})
},
oneOfType (arr) {
if (!isArray(arr)) {
throw new TypeError('[VueTypes error]: You must provide an array as argument')
}
let hasCustomValidators = false
const nativeChecks = arr.reduce((ret, type, i) => {
if (isPlainObject(type)) {
if (type._vueTypes_name === 'oneOf') {
return ret.concat(type.type || [])
}
if (type.type && !isFunction(type.validator)) {
if (isArray(type.type)) return ret.concat(type.type)
ret.push(type.type)
} else if (isFunction(type.validator)) {
hasCustomValidators = true
}
return ret
}
ret.push(type)
return ret
}, [])
if (!hasCustomValidators) {
// we got just native objects (ie: Array, Object)
// delegate to Vue native prop check
return toType('oneOfType', {
type: nativeChecks,
})
}
const typesStr = arr.map((type) => {
if (type && isArray(type.type)) {
return type.type.map(getType)
}
return getType(type)
}).reduce((ret, type) => ret.concat(isArray(type) ? type : [type]), []).join('", "')
return this.custom(function oneOfType (value) {
const valid = arr.some((type) => {
if (type._vueTypes_name === 'oneOf') {
return type.type ? validateType(type.type, value, true) : true
}
return validateType(type, value, true)
})
if (!valid) warn(`oneOfType - value type should be one of "${typesStr}"`)
return valid
})
},
arrayOf (type) {
return toType('arrayOf', {
type: Array,
validator (values) {
const valid = values.every((value) => validateType(type, value))
if (!valid) warn(`arrayOf - value must be an array of "${getType(type)}"`)
return valid
},
})
},
objectOf (type) {
return toType('objectOf', {
type: Object,
validator (obj) {
const valid = Object.keys(obj).every((key) => validateType(type, obj[key]))
if (!valid) warn(`objectOf - value must be an object of "${getType(type)}"`)
return valid
},
})
},
shape (obj) {
const keys = Object.keys(obj)
const requiredKeys = keys.filter((key) => obj[key] && obj[key].required === true)
const type = toType('shape', {
type: Object,
validator (value) {
if (!isPlainObject(value)) {
return false
}
const valueKeys = Object.keys(value)
// check for required keys (if any)
if (requiredKeys.length > 0 && requiredKeys.some((req) => valueKeys.indexOf(req) === -1)) {
warn(`shape - at least one of required properties "${requiredKeys.join('", "')}" is not present`)
return false
}
return valueKeys.every((key) => {
if (keys.indexOf(key) === -1) {
if (this._vueTypes_isLoose === true) return true
warn(`shape - object is missing "${key}" property`)
return false
}
const type = obj[key]
return validateType(type, value[key])
})
},
})
Object.defineProperty(type, '_vueTypes_isLoose', {
enumerable: false,
writable: true,
value: false,
})
Object.defineProperty(type, 'loose', {
get () {
this._vueTypes_isLoose = true
return this
},
enumerable: false,
})
return type
},
}
const typeDefaults = () => ({
func: undefined,
bool: undefined,
string: undefined,
number: undefined,
array: undefined,
object: undefined,
integer: undefined,
})
let currentDefaults = typeDefaults()
Object.defineProperty(VuePropTypes, 'sensibleDefaults', {
enumerable: false,
set (value) {
if (value === false) {
currentDefaults = {}
} else if (value === true) {
currentDefaults = typeDefaults()
} else if (isPlainObject(value)) {
currentDefaults = value
}
},
get () {
return currentDefaults
},
})
export default VuePropTypes

View File

@ -0,0 +1,188 @@
import isPlainObject from 'lodash.isplainobject'
const ObjProto = Object.prototype
const toString = ObjProto.toString
export const hasOwn = ObjProto.hasOwnProperty
const FN_MATCH_REGEXP = /^\s*function (\w+)/
// https://github.com/vuejs/vue/blob/dev/src/core/util/props.js#L159
export const getType = (fn) => {
const type = (fn !== null && fn !== undefined) ? (fn.type ? fn.type : fn) : null
const match = type && type.toString().match(FN_MATCH_REGEXP)
return match && match[1]
}
export const getNativeType = (value) => {
if (value === null || value === undefined) return null
const match = value.constructor.toString().match(FN_MATCH_REGEXP)
return match && match[1]
}
/**
* No-op function
*/
export const noop = () => {}
/**
* Checks for a own property in an object
*
* @param {object} obj - Object
* @param {string} prop - Property to check
*/
export const has = (obj, prop) => hasOwn.call(obj, prop)
/**
* Determines whether the passed value is an integer. Uses `Number.isInteger` if available
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger
* @param {*} value - The value to be tested for being an integer.
* @returns {boolean}
*/
export const isInteger = Number.isInteger || function (value) {
return typeof value === 'number' && isFinite(value) && Math.floor(value) === value
}
/**
* Determines whether the passed value is an Array.
*
* @param {*} value - The value to be tested for being an array.
* @returns {boolean}
*/
export const isArray = Array.isArray || function (value) {
return toString.call(value) === '[object Array]'
}
/**
* Checks if a value is a function
*
* @param {any} value - Value to check
* @returns {boolean}
*/
export const isFunction = (value) => toString.call(value) === '[object Function]'
/**
* Adds a `def` method to the object returning a new object with passed in argument as `default` property
*
* @param {object} type - Object to enhance
*/
export const withDefault = function (type) {
Object.defineProperty(type, 'def', {
value (def) {
if (def === undefined && this.default === undefined) {
this.default = undefined
return this
}
if (!isFunction(def) && !validateType(this, def)) {
warn(`${this._vueTypes_name} - invalid default value: "${def}"`, def)
return this
}
this.default = (isArray(def) || isPlainObject(def)) ? function () {
return def
} : def
return this
},
enumerable: false,
writable: false,
})
}
/**
* Adds a `isRequired` getter returning a new object with `required: true` key-value
*
* @param {object} type - Object to enhance
*/
export const withRequired = function (type) {
Object.defineProperty(type, 'isRequired', {
get () {
this.required = true
return this
},
enumerable: false,
})
}
/**
* Adds `isRequired` and `def` modifiers to an object
*
* @param {string} name - Type internal name
* @param {object} obj - Object to enhance
* @returns {object}
*/
export const toType = (name, obj) => {
Object.defineProperty(obj, '_vueTypes_name', {
enumerable: false,
writable: false,
value: name,
})
withRequired(obj)
withDefault(obj)
if (isFunction(obj.validator)) {
obj.validator = obj.validator.bind(obj)
}
return obj
}
/**
* Validates a given value against a prop type object
*
* @param {Object|*} type - Type to use for validation. Either a type object or a constructor
* @param {*} value - Value to check
* @param {boolean} silent - Silence warnings
* @returns {boolean}
*/
export const validateType = (type, value, silent = false) => {
let typeToCheck = type
let valid = true
let expectedType
if (!isPlainObject(type)) {
typeToCheck = { type }
}
const namePrefix = typeToCheck._vueTypes_name ? (typeToCheck._vueTypes_name + ' - ') : ''
if (hasOwn.call(typeToCheck, 'type') && typeToCheck.type !== null) {
if (isArray(typeToCheck.type)) {
valid = typeToCheck.type.some((type) => validateType(type, value, true))
expectedType = typeToCheck.type.map((type) => getType(type)).join(' or ')
} else {
expectedType = getType(typeToCheck)
if (expectedType === 'Array') {
valid = isArray(value)
} else if (expectedType === 'Object') {
valid = isPlainObject(value)
} else if (expectedType === 'String' || expectedType === 'Number' || expectedType === 'Boolean' || expectedType === 'Function') {
valid = getNativeType(value) === expectedType
} else {
valid = value instanceof typeToCheck.type
}
}
}
if (!valid) {
silent === false && warn(`${namePrefix}value "${value}" should be of type "${expectedType}"`)
return false
}
if (hasOwn.call(typeToCheck, 'validator') && isFunction(typeToCheck.validator)) {
valid = typeToCheck.validator(value)
if (!valid && silent === false) warn(`${namePrefix}custom validation failed`)
return valid
}
return valid
}
let warn = noop
if (process.env.NODE_ENV !== 'production') {
const hasConsole = typeof console !== 'undefined'
warn = (msg) => {
if (hasConsole) {
console.warn(`[VueTypes warn]: ${msg}`)
}
}
}
export { warn }

View File

@ -1,6 +1,7 @@
<script>
import PropTypes from 'vue-types'
import align from 'dom-align'
import clonedeep from 'lodash.clonedeep'
import addEventListener from '../_util/Dom/addEventListener'
import { cloneElement } from '../_util/vnode.js'
import isWindow from './isWindow'
@ -38,7 +39,8 @@ export default {
},
watch: {
'$props': {
handler: function (props, prevProps) {
handler: function (props) {
const prevProps = this.prevProps
this.$nextTick(() => {
let reAlign = false
if (!props.disabled) {
@ -106,16 +108,11 @@ export default {
},
render () {
this.prevProps = clonedeep(this.$props)
const { childrenProps } = this.$props
const child = this.$slots.default[0]
if (childrenProps) {
const newProps = {}
for (const prop in childrenProps) {
if (childrenProps.hasOwnProperty(prop)) {
newProps[prop] = this.props[childrenProps[prop]]
}
}
return cloneElement(child, { props: newProps })
return cloneElement(child, { props: childrenProps })
}
return child
},

View File

@ -7,7 +7,7 @@ export default {
hiddenClassName: PropTypes.string,
},
render () {
const { hiddenClassName, visible } = this.props
const { hiddenClassName, visible } = this.$props
if (hiddenClassName || this.$slots.default.length > 1) {
let cls = ''

View File

@ -1,5 +1,5 @@
<script>
import PropTypes from 'vue-types'
import PropTypes from '../_util/vue-types'
import Align from '../align'
import PopupInner from './PopupInner'
import LazyRenderBox from './LazyRenderBox'
@ -12,10 +12,16 @@ export default {
align: PropTypes.any,
destroyPopupOnHide: PropTypes.bool,
prefixCls: PropTypes.string,
getContainer: PropTypes.func,
},
mounted () {
this.rootNode = this.getPopupDomNode()
this._container = this.getContainer()
this._container.appendChild(this.$el)
},
beforeDestroy () {
this._container && this._container.parentNode.removeChild(this._container)
this._container = null
},
methods: {
onAlign (popupDomNode, align) {
@ -75,7 +81,7 @@ export default {
if (!visible) {
this.currentAlignClassName = null
}
// visible = true
const popupInnerProps = {
props: {
prefixCls,
@ -89,12 +95,10 @@ export default {
ref: 'popupInstance',
style: { ...this.getZIndexStyle() },
}
if (destroyPopupOnHide) {
return (<Animate
component=''
exclusive
transitionAppear
transitionName={this.getTransitionName()}
return (<transition
name={this.getTransitionName()}
>
{visible ? (<Align
target={this.getTarget}
@ -110,23 +114,18 @@ export default {
{$slots.default}
</PopupInner>
</Align>) : null}
</Animate>)
</transition>)
}
popupInnerProps.props.hiddenClassName = hiddenClassName
return (<Animate
component=''
exclusive
transitionAppear
transitionName={this.getTransitionName()}
showProp='xVisible'
return (<transition
name={this.getTransitionName()}
>
<Align
target={this.getTarget}
key='popup'
ref='alignInstance'
monitorWindowResize
xVisible={visible}
childrenProps={{ visible: 'xVisible' }}
childrenProps={{ visible }}
disabled={!visible}
align={align}
onAlign={this.onAlign}
@ -137,7 +136,7 @@ export default {
{$slots.default}
</PopupInner>
</Align>
</Animate>)
</transition>)
},
getZIndexStyle () {
@ -165,15 +164,11 @@ export default {
)
if (maskTransition) {
maskElement = (
<Animate
key='mask'
showProp='visible'
transitionAppear
component=''
transitionName={maskTransition}
<transition
name={maskTransition}
>
{maskElement}
</Animate>
</transition>
)
}
}

View File

@ -6,6 +6,7 @@ export default {
props: {
hiddenClassName: PropTypes.string.def(''),
prefixCls: PropTypes.string,
visible: PropTypes.bool,
},
methods: {
onMouseEnter (e) {

View File

@ -0,0 +1,67 @@
@triggerPrefixCls: rc-trigger-popup;
.@{triggerPrefixCls} {
position: absolute;
left: -9999px;
top: -9999px;
z-index: 1050;
&-hidden {
display: none;
}
.effect() {
animation-duration: 0.3s;
animation-fill-mode: both;
}
&-zoom-enter,&-zoom-appear {
opacity: 0;
.effect();
animation-timing-function: cubic-bezier(0.18, 0.89, 0.32, 1.28);
animation-play-state: paused;
}
&-zoom-leave {
.effect();
animation-timing-function: cubic-bezier(0.6, -0.3, 0.74, 0.05);
animation-play-state: paused;
}
&-zoom-enter&-zoom-enter-active, &-zoom-appear&-zoom-appear-active {
animation-name: rcTriggerZoomIn;
animation-play-state: running;
}
&-zoom-leave&-zoom-leave-active {
animation-name: rcTriggerZoomOut;
animation-play-state: running;
}
@keyframes rcTriggerZoomIn {
0% {
opacity: 0;
transform-origin: 50% 50%;
transform: scale(0, 0);
}
100% {
opacity: 1;
transform-origin: 50% 50%;
transform: scale(1, 1);
}
}
@keyframes rcTriggerZoomOut {
0% {
opacity: 1;
transform-origin: 50% 50%;
transform: scale(1, 1);
}
100% {
opacity: 0;
transform-origin: 50% 50%;
transform: scale(0, 0);
}
}
}
@import "./mask";

View File

@ -0,0 +1,63 @@
.@{triggerPrefixCls} {
&-mask {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
background-color: rgb(55, 55, 55);
background-color: rgba(55, 55, 55, 0.6);
height: 100%;
filter: alpha(opacity=50);
z-index: 1050;
&-hidden {
display: none;
}
}
.fade-effect() {
animation-duration: 0.3s;
animation-fill-mode: both;
animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2);
}
&-fade-enter,&-fade-appear {
opacity: 0;
.fade-effect();
animation-play-state: paused;
}
&-fade-leave {
.fade-effect();
animation-play-state: paused;
}
&-fade-enter&-fade-enter-active,&-fade-appear&-fade-appear-active {
animation-name: rcTriggerMaskFadeIn;
animation-play-state: running;
}
&-fade-leave&-fade-leave-active {
animation-name: rcDialogFadeOut;
animation-play-state: running;
}
@keyframes rcTriggerMaskFadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes rcDialogFadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
}

View File

@ -0,0 +1,57 @@
<script>
import Trigger from '../index'
import '../assets/index.less'
const builtinPlacements = {
left: {
points: ['cr', 'cl'],
},
right: {
points: ['cl', 'cr'],
},
top: {
points: ['bc', 'tc'],
},
bottom: {
points: ['tc', 'bc'],
},
topLeft: {
points: ['bl', 'tl'],
},
topRight: {
points: ['br', 'tr'],
},
bottomRight: {
points: ['tr', 'br'],
},
bottomLeft: {
points: ['tl', 'bl'],
},
}
const popupBorderStyle = {
border: '1px solid red',
padding: '10px',
}
export default {
render () {
return (
<div>
<div style={{ margin: '50px' }}>
<Trigger
popupPlacement='right'
action={['click']}
builtinPlacements={builtinPlacements}
>
<div style={popupBorderStyle} slot='popup' onClick={(e) => { e.stopPropagation() }}>
jjj
</div>
<span href='#' style={{ margin: '20px' }} onClick={console.log}>trigger</span>
</Trigger>
</div>
</div>
)
},
}
</script>

View File

@ -1,3 +1,3 @@
// export this package's api
import Trigger from './src/'
import Trigger from './index.vue'
export default Trigger

View File

@ -1,5 +1,5 @@
<script>
import PropTypes from 'vue-types'
import PropTypes from '../_util/vue-types'
import contains from '../_util/Dom/contains'
import addEventListener from '../_util/Dom/addEventListener'
import Popup from './Popup'
@ -16,8 +16,8 @@ function returnDocument () {
return window.document
}
const ALL_HANDLERS = ['onClick', 'onMouseDown', 'onTouchStart', 'onMouseEnter',
'onMouseLeave', 'onFocus', 'onBlur', 'onContextMenu']
const ALL_HANDLERS = ['click', 'mousedown', 'touchStart', 'mouseenter',
'mouseleave', 'focus', 'blur', 'contextMenu']
const mixins = []
@ -26,11 +26,11 @@ mixins.push(
autoMount: false,
isVisible (instance) {
return instance.state.sPopupVisible
return instance.$data.sPopupVisible
},
isForceRender (instance) {
return instance.props.forceRender
return instance.$props.forceRender
},
getContainer (instance) {
@ -65,7 +65,7 @@ export default {
zIndex: PropTypes.number,
focusDelay: PropTypes.number.def(0),
blurDelay: PropTypes.number.def(0.15),
getPopupContainer: PropTypes.func,
getPopupContainer: Function,
getDocument: PropTypes.func.def(returnDocument),
forceRender: PropTypes.bool,
destroyPopupOnHide: PropTypes.bool.def(false),
@ -74,15 +74,15 @@ export default {
// onPopupAlign: PropTypes.func,
popupAlign: PropTypes.object.def({}),
popupVisible: PropTypes.bool,
defaultPopupVisible: PropTypes.bool.def(false),
maskTransitionName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
]),
maskAnimation: PropTypes.string,
// defaultPopupVisible: PropTypes.bool.def(false),
// maskTransitionName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.object,
// ]),
// maskAnimation: PropTypes.string,
},
mixins: [StateMixin, mixins],
mixins: [StateMixin, ...mixins],
data () {
const props = this.$props
let popupVisible
@ -99,15 +99,14 @@ export default {
beforeCreate () {
ALL_HANDLERS.forEach((h) => {
this[`fire${h}`] = (e) => {
this.fireEvents(h, e)
const ev = `on${h[0].toUpperCase() + h.slice(1)}`
this.fireEvents(ev, e)
}
})
},
mounted () {
this.updated({}, {
sPopupVisible: this.$data.sPopupVisible,
})
this.updatedCal()
},
watch: {
popupVisible (val) {
@ -122,43 +121,8 @@ export default {
},
},
updated (_, prevState) {
const props = this.$props
const state = this.$data
this.renderComponent()
// 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)
}
return
}
this.clearOutsideHandler()
updated () {
this.updatedCal()
},
beforeDestroy () {
@ -166,21 +130,59 @@ export default {
this.clearOutsideHandler()
},
methods: {
onMouseEnter (e) {
this.fireEvents('onMouseEnter', e)
updatedCal () {
const props = this.$props
const state = this.$data
// this.renderComponent()
// 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)
}
return
}
this.clearOutsideHandler()
},
onMouseenter (e) {
this.fireEvents('mouseenter', e)
this.delaySetPopupVisible(true, this.$props.mouseEnterDelay)
},
onMouseLeave (e) {
this.fireEvents('onMouseLeave', e)
onMouseleave (e) {
this.fireEvents('mouseleave', e)
this.delaySetPopupVisible(false, this.$props.mouseLeaveDelay)
},
onPopupMouseEnter () {
onPopupMouseenter () {
this.clearDelayTimer()
},
onPopupMouseLeave (e) {
onPopupMouseleave (e) {
if (e.relatedTarget && !e.relatedTarget.setTimeout &&
this._component &&
this._component.getPopupDomNode &&
@ -191,7 +193,7 @@ export default {
},
onFocus (e) {
this.fireEvents('onFocus', e)
this.fireEvents('focus', e)
// incase focusin and focusout
this.clearDelayTimer()
if (this.isFocusToShow()) {
@ -200,18 +202,18 @@ export default {
}
},
onMouseDown (e) {
this.fireEvents('onMouseDown', e)
onMousedown (e) {
this.fireEvents('mousedown', e)
this.preClickTime = Date.now()
},
onTouchStart (e) {
this.fireEvents('onTouchStart', e)
this.fireEvents('touchStart', e)
this.preTouchTime = Date.now()
},
onBlur (e) {
this.fireEvents('onBlur', e)
this.fireEvents('blur', e)
this.clearDelayTimer()
if (this.isBlurToHide()) {
this.delaySetPopupVisible(false, this.$props.blurDelay)
@ -220,7 +222,7 @@ export default {
onContextMenu (e) {
e.preventDefault()
this.fireEvents('onContextMenu', e)
this.fireEvents('contextMenu', e)
this.setPopupVisible(true)
},
@ -231,7 +233,7 @@ export default {
},
onClick (event) {
this.fireEvents('onClick', event)
this.fireEvents('click', event)
// focus will trigger click
if (this.focusTime) {
let preTime
@ -276,10 +278,10 @@ export default {
},
getRootDomNode () {
return this.$el
return this.$el.children[0] || this.$el
},
getPopupClassNameFromAlign (align) {
getPopupClassFromAlign (align) {
const className = []
const props = this.$props
const { popupPlacement, builtinPlacements, prefixCls } = props
@ -301,21 +303,21 @@ export default {
return popupAlign
},
onPopupAlign () {
this.emit('popupAlign', ...arguments)
this.$emit('popupAlign', ...arguments)
},
getComponent () {
const mouseProps = {}
if (this.isMouseEnterToShow()) {
mouseProps.mouseenter = this.onPopupMouseEnter
mouseProps.mouseenter = this.onPopupMouseenter
}
if (this.isMouseLeaveToHide()) {
mouseProps.mouseleave = this.onPopupMouseLeave
mouseProps.mouseleave = this.onPopupMouseleave
}
const { prefixCls, destroyPopupOnHide, sPopupVisible,
popupStyle, popupClassName, action, onPopupAlign,
popupAnimation, getPopupClassNameFromAlign, getRootDomNode,
popupAnimation, getPopupClassFromAlign, getRootDomNode,
mask, zIndex, popupTransitionName, getPopupAlign,
maskAnimation, maskTransitionName, popup, $slots } = this
maskAnimation, maskTransitionName, popup, $slots, getContainer } = this
const popupProps = {
props: {
prefixCls,
@ -324,13 +326,14 @@ export default {
action,
align: getPopupAlign(),
animation: popupAnimation,
getClassNameFromAlign: getPopupClassNameFromAlign,
getClassNameFromAlign: getPopupClassFromAlign,
getRootDomNode,
mask,
zIndex,
transitionName: popupTransitionName,
maskAnimation,
maskTransitionName,
getContainer,
},
on: {
align: onPopupAlign,
@ -343,6 +346,7 @@ export default {
return (
<Popup
{...popupProps}
ref='popup'
>
{typeof popup === 'function' ? popup() : popup}
{popup === undefined ? $slots.popup : null}
@ -368,10 +372,11 @@ export default {
setPopupVisible (sPopupVisible) {
this.clearDelayTimer()
if (this.$data.sPopupVisible !== sPopupVisible) {
if (this.$props.popupVisible !== undefined) {
if (this.$props.popupVisible === undefined) {
this.setState({
sPopupVisible,
})
this.$forceUpdate()
}
this.$emit('popupVisibleChange', sPopupVisible)
}
@ -420,12 +425,17 @@ export default {
},
createTwoChains (event) {
const childPros = this.$props.children.props
const props = this.$props
if (childPros[event] && props[event]) {
return this[`fire${event}`]
const child = this.$slots.default[0]
let fn = () => {}
if (child && child.data && child.data.on) {
const childEvents = child.data.on
const events = (this.data ? this.data.on : {}) || {}
if (childEvents[event] && events[event]) {
return this[`fire${event}`]
}
fn = childEvents[event] || events[event] || fn
}
return childPros[event] || props[event]
return fn
},
isClickToShow () {
@ -445,12 +455,12 @@ export default {
isMouseEnterToShow () {
const { action, showAction } = this.$props
return action.indexOf('hover') !== -1 || showAction.indexOf('mouseEnter') !== -1
return action.indexOf('hover') !== -1 || showAction.indexOf('mouseenter') !== -1
},
isMouseLeaveToHide () {
const { action, hideAction } = this.$props
return action.indexOf('hover') !== -1 || hideAction.indexOf('mouseLeave') !== -1
return action.indexOf('hover') !== -1 || hideAction.indexOf('mouseleave') !== -1
},
isFocusToShow () {
@ -463,18 +473,17 @@ export default {
return action.indexOf('focus') !== -1 || hideAction.indexOf('blur') !== -1
},
forcePopupAlign () {
if (this.state.popupVisible && this._component && this._component.$refs.alignInstance) {
if (this.$data.sPopupVisible && this._component && this._component.$refs.alignInstance) {
this._component.$refs.alignInstance.forceAlign()
}
},
fireEvents (type, e) {
const childCallback = this.$props.children.props[type]
if (childCallback) {
childCallback(e)
const child = this.$slots.default[0]
if (child && child.data && child.on && child.on[type]) {
child.on[type](e)
}
const callback = this.$props[type]
if (callback) {
callback(e)
if (this.data && this.data.on && this.data.on[type]) {
this.data.on[type](e)
}
},
@ -494,39 +503,44 @@ export default {
if (this.isContextMenuToShow()) {
newChildProps.on.contextMenu = this.onContextMenu
} else {
newChildProps.on.contextMenu = this.createTwoChains('onContextMenu')
newChildProps.on.contextMenu = this.createTwoChains('contextMenu')
}
if (this.isClickToHide() || this.isClickToShow()) {
newChildProps.on.click = this.onClick
newChildProps.on.mousedown = this.onMouseDown
newChildProps.on.mousedown = this.onMousedown
// newChildProps.on.touchStart = this.onTouchStart
} else {
newChildProps.on.click = this.createTwoChains('onClick')
newChildProps.on.mousedown = this.createTwoChains('onMouseDown')
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
newChildProps.on.mouseenter = this.onMouseenter
} else {
newChildProps.on.mouseenter = this.createTwoChains('onMouseEnter')
newChildProps.on.mouseenter = this.createTwoChains('mouseenter')
}
if (this.isMouseLeaveToHide()) {
newChildProps.on.mouseleave = this.onMouseLeave
newChildProps.on.mouseleave = this.onMouseleave
} else {
newChildProps.on.mouseleave = this.createTwoChains('onMouseLeave')
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('onFocus')
newChildProps.on.blur = this.createTwoChains('onBlur')
newChildProps.on.focus = this.createTwoChains('focus')
newChildProps.on.blur = this.createTwoChains('blur')
}
const trigger = cloneElement(child, newChildProps)
return trigger
return (
<span>
{trigger}
{this.getComponent()}
</span>
)
},
}

View File

@ -80,6 +80,7 @@
"eslint-plugin-vue": "^3.13.0",
"lodash.clonedeep": "^4.5.0",
"lodash.debounce": "^4.0.8",
"lodash.isplainobject": "^4.0.6",
"omit.js": "^1.0.0",
"vue-types": "^1.0.2",
"warning": "^3.0.0"