From 3af108ffcba29b3c005872c052bc799b1ba92867 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Wed, 17 Jan 2018 16:12:53 +0800 Subject: [PATCH] add menu --- components/_util/BaseMixin.js | 10 +- components/_util/vnode.js | 65 +++++------ components/menu/MenuItem.vue | 3 +- components/menu/demo/antd.vue | 6 +- components/menu/demo/horizontal.vue | 1 + components/menu/src/Menu.vue | 34 +++--- components/menu/src/MenuItem.vue | 18 ++- components/menu/src/MenuMixin.js | 8 +- components/menu/src/SubMenu.vue | 161 +++++++++++++++++---------- components/menu/src/SubPopupMenu.vue | 2 +- components/trigger/Popup.vue | 15 ++- components/trigger/index.vue | 66 ++++++----- 12 files changed, 213 insertions(+), 176 deletions(-) diff --git a/components/_util/BaseMixin.js b/components/_util/BaseMixin.js index 66d6bfa94..7c3206312 100644 --- a/components/_util/BaseMixin.js +++ b/components/_util/BaseMixin.js @@ -8,8 +8,14 @@ export default { }, __emit () { // 直接调用listeners,底层组件不需要vueTool记录events const args = [].slice.call(arguments, 0) - if (args.length && this.$listeners[args[0]]) { - this.$listeners[args[0]](...args.slice(1)) + const filterEvent = [] + const eventName = args[0] + if (args.length && this.$listeners[eventName]) { + if (filterEvent.includes(eventName)) { + this.$emit(eventName, ...args.slice(1)) + } else { + this.$listeners[eventName](...args.slice(1)) + } } }, }, diff --git a/components/_util/vnode.js b/components/_util/vnode.js index ecf245b82..64784e1bf 100644 --- a/components/_util/vnode.js +++ b/components/_util/vnode.js @@ -1,34 +1,9 @@ -import clonedeep from 'lodash.clonedeep' -// export function cloneVNode (vnode, deep) { -// const cloned = new vnode.constructor( -// vnode.tag, -// clonedeep(vnode.data), -// vnode.children, -// vnode.text, -// vnode.elm, -// vnode.context, -// clonedeep(vnode.componentOptions), -// vnode.asyncFactory -// ) -// cloned.ns = vnode.ns -// cloned.isStatic = vnode.isStatic -// cloned.key = vnode.key -// cloned.isComment = vnode.isComment -// cloned.isCloned = true -// if (deep && vnode.children) { -// cloned.children = cloneVNodes(vnode.children, deep) -// } -// return cloned -// } +import cloneDeep from 'lodash.clonedeep' export function cloneVNode (vnode, deep) { const componentOptions = vnode.componentOptions - // if (componentOptions) { - // componentOptions.propsData = componentOptions.propsData ? clonedeep(componentOptions.propsData) : componentOptions.propsData - // } - const cloned = new vnode.constructor( vnode.tag, - clonedeep(vnode.data), + vnode.data, vnode.children, vnode.text, vnode.elm, @@ -66,21 +41,25 @@ export function cloneVNodes (vnodes, deep) { export function cloneElement (n, nodeProps, clone) { const node = clone ? cloneVNode(n, true) : n - const { props = {}, key, on = {}} = nodeProps - if (node.componentOptions) { - node.componentOptions.propsData = node.componentOptions.propsData || {} - node.componentOptions.listeners = node.componentOptions.listeners || {} - Object.assign(node.componentOptions.propsData, props) - Object.assign(node.componentOptions.listeners, on) - } - + const { props = {}, key, on = {}, addChildren } = nodeProps const data = node.data || {} const { style = data.style, class: cls = data.class, attrs = data.attrs, ref, } = nodeProps - node.data = Object.assign(data, { style, attrs, class: cls, on: { ...(data.on || {}), ...on }}) + node.data = Object.assign(data, { style, attrs, class: cls }) + if (node.componentOptions) { + node.componentOptions.propsData = node.componentOptions.propsData || {} + node.componentOptions.listeners = node.componentOptions.listeners || {} + node.componentOptions.propsData = { ...node.componentOptions.propsData, ...props } + node.componentOptions.listeners = { ...node.componentOptions.listeners, ...on } + addChildren && node.componentOptions.children.push(addChildren) + } else { + addChildren && (node.children = [...(node.children || []), addChildren]) + node.data.on = { ...(node.data.on || {}), ...on } + } + if (key !== undefined) { node.key = key node.data.key = key @@ -105,3 +84,17 @@ export function getClass (ele) { export function getStyle (ele) { return ele.data && (ele.data.style || ele.data.staticStyle) } + +export function filterEmpty (children = []) { + return children.filter(c => c.tag || c.text.trim() !== '') +} + +export function getEvents (child) { + let events = {} + if (child.componentOptions && child.componentOptions.listeners) { + events = child.componentOptions.listeners + } else if (child.data && child.data.on) { + events = child.data.on + } + return events +} diff --git a/components/menu/MenuItem.vue b/components/menu/MenuItem.vue index 006d9f049..a7e6c39c2 100644 --- a/components/menu/MenuItem.vue +++ b/components/menu/MenuItem.vue @@ -2,6 +2,7 @@ import { Item, itemProps } from './src/index' import { getClass, getStyle } from '../_util/vnode' import Tooltip from '../tooltip' +import { getComponentFromProp } from '../_util/props-util' export default { props: itemProps, @@ -14,7 +15,7 @@ export default { this.$refs.menuItem.onKeyDown(e) }, }, - render () { + render (h) { const { inlineCollapsed, $props: props, $slots, $attrs: attrs, $listeners } = this const itemProps = { props, diff --git a/components/menu/demo/antd.vue b/components/menu/demo/antd.vue index e1083b02e..d8ab12e58 100644 --- a/components/menu/demo/antd.vue +++ b/components/menu/demo/antd.vue @@ -76,7 +76,7 @@ export default { console.log('onOpenChange', value, this.$refs) } const commonMenu = () => ( - + console.log('click', e)}> sub menu}> 0-1 @@ -103,7 +103,7 @@ export default { {commonMenu()} -

horizontal and click

+ {/*

horizontal and click

{commonMenu()} -
+ */} ) diff --git a/components/menu/demo/horizontal.vue b/components/menu/demo/horizontal.vue index 93fdaec9e..d46101a8f 100644 --- a/components/menu/demo/horizontal.vue +++ b/components/menu/demo/horizontal.vue @@ -43,6 +43,7 @@ export default { }, methods: { handleClick (e) { + console.log(e) this.current = e.key }, }, diff --git a/components/menu/src/Menu.vue b/components/menu/src/Menu.vue index 739d23992..0f6562ee0 100644 --- a/components/menu/src/Menu.vue +++ b/components/menu/src/Menu.vue @@ -49,19 +49,19 @@ const Menu = { }, }, methods: { - onDestroy (key) { - const state = this.$data - const sSelectedKeys = state.sSelectedKeys - const sOpenKeys = state.sOpenKeys - let index = sSelectedKeys.indexOf(key) - if (!hasProp(this, 'selectedKeys') && index !== -1) { - sSelectedKeys.splice(index, 1) - } - index = sOpenKeys.indexOf(key) - if (!hasProp(this, 'openKeys') && index !== -1) { - sOpenKeys.splice(index, 1) - } - }, + // onDestroy (key) { + // const state = this.$data + // const sSelectedKeys = state.sSelectedKeys + // const sOpenKeys = state.sOpenKeys + // let index = sSelectedKeys.indexOf(key) + // if (!hasProp(this, 'selectedKeys') && index !== -1) { + // sSelectedKeys.splice(index, 1) + // } + // index = sOpenKeys.indexOf(key) + // if (!hasProp(this, 'openKeys') && index !== -1) { + // sOpenKeys.splice(index, 1) + // } + // }, onSelect (selectInfo) { const props = this.$props @@ -90,7 +90,7 @@ const Menu = { this.__emit('click', e) }, - onOpenChange (e_) { + onOpenChange (event) { const sOpenKeys = this.$data.sOpenKeys.concat() let changed = false const processSingle = (e) => { @@ -109,11 +109,11 @@ const Menu = { } changed = changed || oneChanged } - if (Array.isArray(e_)) { + if (Array.isArray(event)) { // batch change call - e_.forEach(processSingle) + event.forEach(processSingle) } else { - processSingle(e_) + processSingle(event) } if (changed) { if (!hasProp(this, 'openKeys')) { diff --git a/components/menu/src/MenuItem.vue b/components/menu/src/MenuItem.vue index 6a4a8be20..87c246fc9 100644 --- a/components/menu/src/MenuItem.vue +++ b/components/menu/src/MenuItem.vue @@ -15,7 +15,7 @@ const props = { level: PropTypes.number.def(1), mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']).def('vertical'), parentMenu: PropTypes.object, - clearSubMenuTimers: PropTypes.func.def(noop), + // clearSubMenuTimers: PropTypes.func.def(noop), } const MenuItem = { name: 'MenuItem', @@ -30,12 +30,6 @@ const MenuItem = { this.__emit('destroy', props.eventKey) }, methods: { - __emit () { // 直接调用listeners,底层组件不需要vueTool记录events - const args = [].slice.call(arguments, 0) - if (args.length && this.$listeners[args[0]]) { - this.$listeners[args[0]](...args.slice(1)) - } - }, onKeyDown (e) { const keyCode = e.keyCode if (keyCode === KeyCode.ENTER) { @@ -57,10 +51,10 @@ const MenuItem = { }, onMouseEnter (e) { - const { eventKey, parentMenuContext } = this - if (parentMenuContext && parentMenuContext.subMenuInstance) { - parentMenuContext.subMenuInstance.clearSubMenuTimers() - } + const { eventKey } = this + // if (parentMenuContext && parentMenuContext.subMenuInstance) { + // parentMenuContext.subMenuInstance.clearSubMenuTimers() + // } this.__emit('itemHover', { key: eventKey, hover: true, @@ -80,6 +74,7 @@ const MenuItem = { item: this, domEvent: e, } + this.__emit('click', info) if (multiple) { if (selected) { @@ -130,6 +125,7 @@ const MenuItem = { 'aria-disabled': props.disabled, } let mouseEvent = {} + if (!props.disabled) { mouseEvent = { click: this.onClick, diff --git a/components/menu/src/MenuMixin.js b/components/menu/src/MenuMixin.js index d3c548009..e33792008 100644 --- a/components/menu/src/MenuMixin.js +++ b/components/menu/src/MenuMixin.js @@ -167,16 +167,16 @@ export default { itemHover: this.onItemHover, openChange: this.onOpenChange, deselect: this.onDeselect, - destroy: this.onDestroy, + // destroy: this.onDestroy, select: this.onSelect, }, } if (props.mode === 'inline') { newChildProps.props.triggerSubMenuAction = 'click' } - if (!extraProps.isRootMenu) { - newChildProps.props.clearSubMenuTimers = this.clearSubMenuTimers - } + // if (!extraProps.isRootMenu) { + // newChildProps.props.clearSubMenuTimers = this.clearSubMenuTimers + // } return cloneElement(child, newChildProps) }, diff --git a/components/menu/src/SubMenu.vue b/components/menu/src/SubMenu.vue index de11f5a32..cba74f375 100644 --- a/components/menu/src/SubMenu.vue +++ b/components/menu/src/SubMenu.vue @@ -21,7 +21,7 @@ export default { name: 'SubMenu', props: { mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']).def('vertical'), - title: PropTypes.any.def(''), + title: PropTypes.any, selectedKeys: PropTypes.array.def([]), openKeys: PropTypes.array.def([]), openChange: PropTypes.func.def(noop), @@ -42,6 +42,7 @@ export default { subMenuCloseDelay: PropTypes.number.def(0.1), level: PropTypes.number.def(1), inlineIndent: PropTypes.number.def(24), + openTransitionName: PropTypes.string, }, inject: { parentMenuContext: { default: undefined }, @@ -63,10 +64,16 @@ export default { }, beforeDestroy () { - const { eventKey, parentMenuContext } = this + const { eventKey } = this this.__emit('destroy', eventKey) - if (parentMenuContext.subMenuInstance === this) { - this.clearSubMenuTimers() + // if (parentMenuContext.subMenuInstance === this) { + // this.clearSubMenuTimers() + // } + if (this.minWidthTimeout) { + clearTimeout(this.minWidthTimeout) + } + if (this.mouseenterTimeout) { + clearTimeout(this.mouseenterTimeout) } }, methods: { @@ -75,15 +82,16 @@ export default { if (mode !== 'horizontal' || !isRootMenu || !this.isOpen()) { return } - setTimeout(() => { - if (!this.subMenuTitle || !this.menuInstance) { + const self = this + this.minWidthTimeout = setTimeout(() => { + if (!self.$refs.subMenuTitle || !self.$refs.menuInstance) { return } - const popupMenu = this.$refs.menuInstance.$el - if (popupMenu.offsetWidth >= this.subMenuTitle.offsetWidth) { + const popupMenu = self.$refs.menuInstance.$el + if (popupMenu.offsetWidth >= self.$refs.subMenuTitle.offsetWidth) { return } - popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px` + popupMenu.style.minWidth = `${self.$refs.subMenuTitle.offsetWidth}px` }, 0) }, @@ -136,7 +144,7 @@ export default { onMouseEnter (e) { const { eventKey: key } = this.$props - this.clearSubMenuLeaveTimer() + // this.clearSubMenuLeaveTimer() this.setState({ defaultActiveFirst: false, }) @@ -152,20 +160,24 @@ export default { parentMenuContext, } = this parentMenuContext.subMenuInstance = this - parentMenuContext.subMenuLeaveFn = () => { - // trigger mouseleave - this.__emit('mouseleave', { - key: eventKey, - domEvent: e, - }) - } + // parentMenuContext.subMenuLeaveFn = () => { + // // trigger mouseleave + // this.__emit('mouseleave', { + // key: eventKey, + // domEvent: e, + // }) + // } + this.__emit('mouseleave', { + key: eventKey, + domEvent: e, + }) // prevent popup menu and submenu gap - parentMenuContext.subMenuLeaveTimer = setTimeout(parentMenuContext.subMenuLeaveFn, 100) + // parentMenuContext.subMenuLeaveTimer = setTimeout(parentMenuContext.subMenuLeaveFn, 100) }, onTitleMouseEnter (domEvent) { const { eventKey: key } = this.$props - this.clearSubMenuTitleLeaveTimer() + // this.clearSubMenuTitleLeaveTimer() this.__emit('itemHover', { key, hover: true, @@ -179,17 +191,25 @@ export default { onTitleMouseLeave (e) { const { eventKey, parentMenuContext } = this parentMenuContext.subMenuInstance = this - parentMenuContext.subMenuTitleLeaveFn = () => { - this.__emit('itemHover', { - key: eventKey, - hover: false, - }) - this.__emit('titleMouseleave', { - key: eventKey, - domEvent: e, - }) - } - parentMenuContext.subMenuTitleLeaveTimer = setTimeout(parentMenuContext.subMenuTitleLeaveFn, 100) + this.__emit('itemHover', { + key: eventKey, + hover: false, + }) + this.__emit('titleMouseleave', { + key: eventKey, + domEvent: e, + }) + // parentMenuContext.subMenuTitleLeaveFn = () => { + // this.__emit('itemHover', { + // key: eventKey, + // hover: false, + // }) + // this.__emit('titleMouseleave', { + // key: eventKey, + // domEvent: e, + // }) + // } + // parentMenuContext.subMenuTitleLeaveTimer = setTimeout(parentMenuContext.subMenuTitleLeaveFn, 100) }, onTitleClick (e) { @@ -239,38 +259,57 @@ export default { } }, + // triggerOpenChange (open, type) { + // const key = this.$props.eventKey + // this.__emit('openChange', { + // key, + // item: this, + // trigger: type, + // open, + // }) + // }, triggerOpenChange (open, type) { const key = this.$props.eventKey - this.__emit('openChange', { - key, - item: this, - trigger: type, - open, - }) - }, - - clearSubMenuTimers () { - this.clearSubMenuLeaveTimer() - this.clearSubMenuTitleLeaveTimer() - }, - - clearSubMenuTitleLeaveTimer () { - const parentMenuContext = this.parentMenuContext - if (parentMenuContext.subMenuTitleLeaveTimer) { - clearTimeout(parentMenuContext.subMenuTitleLeaveTimer) - parentMenuContext.subMenuTitleLeaveTimer = null - parentMenuContext.subMenuTitleLeaveFn = null + const openChange = () => { + this.__emit('openChange', { + key, + item: this, + trigger: type, + open, + }) + } + if (type === 'mouseenter') { + // make sure mouseenter happen after other menu item's mouseleave + this.mouseenterTimeout = setTimeout(() => { + openChange() + }, 0) + } else { + openChange() } }, - clearSubMenuLeaveTimer () { - const parentMenuContext = this.parentMenuContext - if (parentMenuContext.subMenuLeaveTimer) { - clearTimeout(parentMenuContext.subMenuLeaveTimer) - parentMenuContext.subMenuLeaveTimer = null - parentMenuContext.subMenuLeaveFn = null - } - }, + // clearSubMenuTimers () { + // this.clearSubMenuLeaveTimer() + // this.clearSubMenuTitleLeaveTimer() + // }, + + // clearSubMenuTitleLeaveTimer () { + // const parentMenuContext = this.parentMenuContext + // if (parentMenuContext.subMenuTitleLeaveTimer) { + // clearTimeout(parentMenuContext.subMenuTitleLeaveTimer) + // parentMenuContext.subMenuTitleLeaveTimer = null + // parentMenuContext.subMenuTitleLeaveFn = null + // } + // }, + + // clearSubMenuLeaveTimer () { + // const parentMenuContext = this.parentMenuContext + // if (parentMenuContext.subMenuLeaveTimer) { + // clearTimeout(parentMenuContext.subMenuLeaveTimer) + // parentMenuContext.subMenuLeaveTimer = null + // parentMenuContext.subMenuLeaveFn = null + // } + // }, isChildrenSelected () { const ret = { find: false } @@ -284,7 +323,7 @@ export default { renderChildren (children, vShow) { const props = this.$props const isOpen = this.isOpen() - const { select, deselect, destroy, openChange } = this.$listeners + const { select, deselect, openChange } = this.$listeners const subPopupMenuProps = { props: { mode: props.mode === 'horizontal' ? 'vertical' : props.mode, @@ -304,11 +343,11 @@ export default { defaultActiveFirst: this.$data.defaultActiveFirst, multiple: props.multiple, prefixCls: props.rootPrefixCls, - clearSubMenuTimers: this.clearSubMenuTimers, + // clearSubMenuTimers: this.clearSubMenuTimers, }, on: { click: this.onSubMenuClick, - select, deselect, destroy, openChange, + select, deselect, openChange, }, id: this._menuId, ref: 'menuInstance', @@ -448,7 +487,7 @@ export default { onPopupVisibleChange={this.onPopupVisibleChange} forceRender={props.forceSubMenuRender} // popupTransitionName='rc-menu-open-slide-up' - popupAnimation={animProps} + popupAnimation={transitionProps} >