From 2e07f98d4063dac1b37608a91af28c33beabdb1e Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Mon, 25 Dec 2017 18:08:36 +0800 Subject: [PATCH] trigger --- components/_util/getContainerRenderMixin.js | 31 ++- components/_util/vnode.js | 14 +- components/_util/vue-types/index.js | 258 ++++++++++++++++++++ components/_util/vue-types/utils.js | 188 ++++++++++++++ components/align/Align.vue | 13 +- components/trigger/LazyRenderBox.vue | 2 +- components/trigger/Popup.vue | 45 ++-- components/trigger/PopupInner.vue | 1 + components/trigger/assets/index.less | 67 +++++ components/trigger/assets/mask.less | 63 +++++ components/trigger/demo/nested.vue | 57 +++++ components/trigger/index.js | 2 +- components/trigger/index.vue | 216 ++++++++-------- package.json | 1 + 14 files changed, 805 insertions(+), 153 deletions(-) create mode 100644 components/_util/vue-types/index.js create mode 100644 components/_util/vue-types/utils.js create mode 100644 components/trigger/assets/index.less create mode 100644 components/trigger/assets/mask.less create mode 100644 components/trigger/demo/nested.vue diff --git a/components/_util/getContainerRenderMixin.js b/components/_util/getContainerRenderMixin.js index 0a8f8b54c..a51b6ffe7 100644 --- a/components/_util/getContainerRenderMixin.js +++ b/components/_util/getContainerRenderMixin.js @@ -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 }, }, } diff --git a/components/_util/vnode.js b/components/_util/vnode.js index d6cb01df9..fa8756045 100644 --- a/components/_util/vnode.js +++ b/components/_util/vnode.js @@ -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 } diff --git a/components/_util/vue-types/index.js b/components/_util/vue-types/index.js new file mode 100644 index 000000000..05ecc25a8 --- /dev/null +++ b/components/_util/vue-types/index.js @@ -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 || '<>', { + 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 diff --git a/components/_util/vue-types/utils.js b/components/_util/vue-types/utils.js new file mode 100644 index 000000000..c661d0dc2 --- /dev/null +++ b/components/_util/vue-types/utils.js @@ -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 } diff --git a/components/align/Align.vue b/components/align/Align.vue index ed63a80f6..a039d36a1 100644 --- a/components/align/Align.vue +++ b/components/align/Align.vue @@ -1,6 +1,7 @@ diff --git a/components/trigger/index.js b/components/trigger/index.js index 0f504fc7d..c519c9a33 100644 --- a/components/trigger/index.js +++ b/components/trigger/index.js @@ -1,3 +1,3 @@ // export this package's api -import Trigger from './src/' +import Trigger from './index.vue' export default Trigger diff --git a/components/trigger/index.vue b/components/trigger/index.vue index 6fd73affd..862d552e0 100644 --- a/components/trigger/index.vue +++ b/components/trigger/index.vue @@ -1,5 +1,5 @@