From 2cce6a5f5599f2c8e0882d01daceefb44974ad7a Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Mon, 5 Nov 2018 21:03:25 +0800 Subject: [PATCH] feat: update vc-menu to 7.4.19 --- components/_util/props-util.js | 8 +- components/vc-menu/DOMWrap.jsx | 317 +++++++++++++++++++++++--- components/vc-menu/Menu.jsx | 91 ++------ components/vc-menu/MenuItem.jsx | 12 +- components/vc-menu/SubMenu.jsx | 14 +- components/vc-menu/SubPopupMenu.jsx | 97 +++++--- components/vc-menu/assets/index.less | 14 +- components/vc-menu/commonPropsType.js | 4 + components/vc-menu/index.js | 2 +- components/vc-menu/util.js | 17 ++ package.json | 1 + 11 files changed, 438 insertions(+), 139 deletions(-) diff --git a/components/_util/props-util.js b/components/_util/props-util.js index 9fddb4427..ca17fae78 100644 --- a/components/_util/props-util.js +++ b/components/_util/props-util.js @@ -88,19 +88,19 @@ const getOptionProps = (instance) => { return filterProps($props, $options.propsData) } -const getComponentFromProp = (instance, prop) => { +const getComponentFromProp = (instance, prop, options) => { if (instance.$createElement) { const h = instance.$createElement const temp = instance[prop] if (temp !== undefined) { - return typeof temp === 'function' ? temp(h) : temp + return typeof temp === 'function' ? temp(h, options) : temp } - return instance.$slots[prop] + return instance.$slots[prop] || (instance.$scopedSlots[prop] && instance.$scopedSlots[prop](options)) || undefined } else { const h = instance.context.$createElement const temp = getPropsData(instance)[prop] if (temp !== undefined) { - return typeof temp === 'function' ? temp(h) : temp + return typeof temp === 'function' ? temp(h, options) : temp } const slotsProp = [] const componentOptions = instance.componentOptions || {}; diff --git a/components/vc-menu/DOMWrap.jsx b/components/vc-menu/DOMWrap.jsx index d51569d11..647f4fb1e 100644 --- a/components/vc-menu/DOMWrap.jsx +++ b/components/vc-menu/DOMWrap.jsx @@ -1,41 +1,304 @@ +import PropTypes from '../_util/vue-types' +import ResizeObserver from 'resize-observer-polyfill' +import SubMenu from './SubMenu' +import BaseMixin from '../_util/BaseMixin' +import { getWidth, setStyle, menuAllProps } from './util' +import { cloneElement } from '../_util/vnode' +import { getClass, getPropsData, filterEmpty } from '../_util/props-util' -import omit from 'omit.js' -export default { +const canUseDOM = !!( + typeof window !== 'undefined' && + window.document && + window.document.createElement +) + +const MENUITEM_OVERFLOWED_CLASSNAME = 'menuitem-overflowed' + +// Fix ssr +if (canUseDOM) { + require('mutationobserver-shim') +} + +const DOMWrap = { name: 'DOMWrap', - props: { - visible: { - type: Boolean, - default: false, - }, - tag: { - type: String, - default: 'div', - }, - hiddenClassName: { - type: String, - default: '', - }, + mixins: [BaseMixin], + data () { + this.resizeObserver = null + this.mutationObserver = null + + // original scroll size of the list + this.originalTotalWidth = 0 + + // copy of overflowed items + this.overflowedItems = [] + + // cache item of the original items (so we can track the size and order) + this.menuItemSizes = [] + return { + lastVisibleIndex: undefined, + } }, - computed: { - class () { - const { visible, hiddenClassName } = this.$props - return { - // [hiddenClassName]: !visible, + + mounted () { + this.$nextTick(() => { + this.setChildrenWidthAndResize() + if (this.level === 1 && this.mode === 'horizontal') { + const menuUl = this.$el + if (!menuUl) { + return + } + this.resizeObserver = new ResizeObserver(entries => { + entries.forEach(this.setChildrenWidthAndResize) + }); + + [].slice.call(menuUl.children).concat(menuUl).forEach(el => { + this.resizeObserver.observe(el) + }) + + if (typeof MutationObserver !== 'undefined') { + this.mutationObserver = new MutationObserver(() => { + this.resizeObserver.disconnect(); + [].slice.call(menuUl.children).concat(menuUl).forEach(el => { + this.resizeObserver.observe(el) + }) + this.setChildrenWidthAndResize() + }) + this.mutationObserver.observe( + menuUl, + { attributes: false, childList: true, subTree: false } + ) + } } + }) + }, + + beforeDestroy () { + if (this.resizeObserver) { + this.resizeObserver.disconnect() + } + if (this.mutationObserver) { + this.resizeObserver.disconnect() + } + }, + methods: { + // get all valid menuItem nodes + getMenuItemNodes () { + const { prefixCls } = this.$props + const ul = this.$el + if (!ul) { + return [] + } + + // filter out all overflowed indicator placeholder + return [].slice.call(ul.children) + .filter(node => { + return node.className.split(' ').indexOf(`${prefixCls}-overflowed-submenu`) < 0 + }) + }, + + getOverflowedSubMenuItem (keyPrefix, overflowedItems, renderPlaceholder) { + const { overflowedIndicator, level, mode, prefixCls, theme } = this.$props + if (level !== 1 || mode !== 'horizontal') { + return null + } + // put all the overflowed item inside a submenu + // with a title of overflow indicator ('...') + const copy = this.$slots.default[0] + const { title, eventKey, ...rest } = getPropsData(copy) + + let style = {} + let key = `${keyPrefix}-overflowed-indicator` + + if (overflowedItems.length === 0 && renderPlaceholder !== true) { + style = { + display: 'none', + } + } else if (renderPlaceholder) { + style = { + visibility: 'hidden', + // prevent from taking normal dom space + position: 'absolute', + } + key = `${key}-placeholder` + } + + const popupClassName = theme ? `${prefixCls}-${theme}` : '' + const subMenuProps = { + props: { + title, + overflowedIndicator, + popupClassName, + eventKey: `${keyPrefix}-overflowed-indicator`, + disabled: false, + }, + class: `${prefixCls}-overflowed-submenu`, + key, + style, + on: copy.$listeners, + } + menuAllProps.props.forEach(k => { + if (rest[k] !== undefined) { + subMenuProps.props[k] = rest[k] + } + }) + + return ( + + {overflowedItems} + + ) + }, + + // memorize rendered menuSize + setChildrenWidthAndResize () { + if (this.mode !== 'horizontal') { + return + } + const ul = this.$el + + if (!ul) { + return + } + + const ulChildrenNodes = ul.children + + if (!ulChildrenNodes || ulChildrenNodes.length === 0) { + return + } + + const lastOverflowedIndicatorPlaceholder = ul.children[ulChildrenNodes.length - 1] + + // need last overflowed indicator for calculating length; + setStyle(lastOverflowedIndicatorPlaceholder, 'display', 'inline-block') + + const menuItemNodes = this.getMenuItemNodes() + + // reset display attribute for all hidden elements caused by overflow to calculate updated width + // and then reset to original state after width calculation + + const overflowedItems = menuItemNodes + .filter(c => c.className.split(' ').indexOf(MENUITEM_OVERFLOWED_CLASSNAME) >= 0) + + overflowedItems.forEach(c => { + setStyle(c, 'display', 'inline-block') + }) + + this.menuItemSizes = menuItemNodes.map(c => getWidth(c)) + + overflowedItems.forEach(c => { + setStyle(c, 'display', 'none') + }) + this.overflowedIndicatorWidth = getWidth(ul.children[ul.children.length - 1]) + this.originalTotalWidth = this.menuItemSizes.reduce((acc, cur) => acc + cur, 0) + this.handleResize() + // prevent the overflowed indicator from taking space; + setStyle(lastOverflowedIndicatorPlaceholder, 'display', 'none') + }, + + handleResize () { + if (this.mode !== 'horizontal') { + return + } + + const ul = this.$el + if (!ul) { + return + } + const width = getWidth(ul) + + this.overflowedItems = [] + let currentSumWidth = 0 + + // index for last visible child in horizontal mode + let lastVisibleIndex + + if (this.originalTotalWidth > width) { + lastVisibleIndex = -1 + + this.menuItemSizes.forEach(liWidth => { + currentSumWidth += liWidth + if (currentSumWidth + this.overflowedIndicatorWidth <= width) { + lastVisibleIndex++ + } + }) + } + + this.setState({ lastVisibleIndex }) + }, + + renderChildren (children) { + // need to take care of overflowed items in horizontal mode + const { lastVisibleIndex } = this.$data + const className = getClass(this) + return (children || []).reduce((acc, childNode, index) => { + let item = childNode + const eventKey = getPropsData(childNode).eventKey + if (this.mode === 'horizontal') { + let overflowed = this.getOverflowedSubMenuItem(eventKey, []) + if (lastVisibleIndex !== undefined && + className[`${this.prefixCls}-root`] + ) { + if (index > lastVisibleIndex) { + item = cloneElement( + childNode, + // 这里修改 eventKey 是为了防止隐藏状态下还会触发 openkeys 事件 + { + style: { display: 'none' }, + props: { eventKey: `${eventKey}-hidden` }, + class: { ...getClass(childNode), [MENUITEM_OVERFLOWED_CLASSNAME]: true }, + }, + ) + } + if (index === lastVisibleIndex + 1) { + this.overflowedItems = children.slice(lastVisibleIndex + 1).map(c => { + return cloneElement( + c, + // children[index].key will become '.$key' in clone by default, + // we have to overwrite with the correct key explicitly + { key: getPropsData(c).eventKey, props: { mode: 'vertical-left' }}, + ) + }) + + overflowed = this.getOverflowedSubMenuItem( + eventKey, + this.overflowedItems, + ) + } + } + + const ret = [...acc, overflowed, item] + + if (index === children.length - 1) { + // need a placeholder for calculating overflowed indicator width + ret.push(this.getOverflowedSubMenuItem(eventKey, [], true)) + } + return ret + } + return [...acc, item] + }, []) }, }, + render () { - const otherProps = omit(this.$props, [ - 'tag', - 'hiddenClassName', - 'visible', - ]) const Tag = this.$props.tag const tagProps = { - attr: { ...otherProps, ...this.$attrs }, on: this.$listeners, } - return {this.$slots.default} + return {this.renderChildren(filterEmpty(this.$slots.default))} }, } +DOMWrap.props = { + mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), + prefixCls: PropTypes.string, + level: PropTypes.number, + theme: PropTypes.string, + overflowedIndicator: PropTypes.node, + visible: PropTypes.bool, + hiddenClassName: PropTypes.string, + tag: PropTypes.string.def('div'), +} + +export default DOMWrap diff --git a/components/vc-menu/Menu.jsx b/components/vc-menu/Menu.jsx index ab92158b1..2a1920fe3 100644 --- a/components/vc-menu/Menu.jsx +++ b/components/vc-menu/Menu.jsx @@ -2,7 +2,7 @@ import PropTypes from '../_util/vue-types' import { Provider, create } from '../_util/store' import { default as SubPopupMenu, getActiveKey } from './SubPopupMenu' import BaseMixin from '../_util/BaseMixin' -import hasProp, { getOptionProps } from '../_util/props-util' +import hasProp, { getOptionProps, getComponentFromProp } from '../_util/props-util' import commonPropsType from './commonPropsType' const Menu = { @@ -33,47 +33,13 @@ const Menu = { // this.isRootMenu = true // 声明在props上 return {} }, - watch: { - selectedKeys (val) { - this.store.setState({ - selectedKeys: val || [], - }) - }, - openKeys (val) { - this.store.setState({ - openKeys: val || [], - }) - }, - // '$props': { - // handler: function (nextProps) { - // if (hasProp(this, 'selectedKeys')) { - // this.setState({ - // sSelectedKeys: nextProps.selectedKeys || [], - // }) - // } - // if (hasProp(this, 'openKeys')) { - // this.setState({ - // sOpenKeys: nextProps.openKeys || [], - // }) - // } - // }, - // deep: true, - // }, + mounted () { + this.updateMiniStore() + }, + updated () { + this.updateMiniStore() }, 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) - // } - // }, onSelect (selectInfo) { const props = this.$props @@ -170,35 +136,19 @@ const Menu = { } return transitionName }, - - // isInlineMode () { - // return this.$props.mode === 'inline' - // }, - - // lastOpenSubMenu () { - // let lastOpen = [] - // const { sOpenKeys } = this.$data - // if (sOpenKeys.length) { - // lastOpen = this.getFlatInstanceArray().filter((c) => { - // return c && sOpenKeys.indexOf(c.eventKey) !== -1 - // }) - // } - // return lastOpen[0] - // }, - - // renderMenuItem (c, i, subIndex) { - // if (!c) { - // return null - // } - // const state = this.$data - // const extraProps = { - // openKeys: state.sOpenKeys, - // selectedKeys: state.sSelectedKeys, - // triggerSubMenuAction: this.$props.triggerSubMenuAction, - // isRootMenu: this.isRootMenu, - // } - // return this.renderCommonMenuItem(c, i, subIndex, extraProps) - // }, + updateMiniStore () { + const props = getOptionProps(this) + if ('selectedKeys' in props) { + this.store.setState({ + selectedKeys: props.selectedKeys || [], + }) + } + if ('openKeys' in props) { + this.store.setState({ + openKeys: props.openKeys || [], + }) + } + }, }, render () { @@ -206,6 +156,9 @@ const Menu = { const subPopupMenuProps = { props: { ...props, + itemIcon: getComponentFromProp(this, 'itemIcon', props), + expandIcon: getComponentFromProp(this, 'expandIcon', props), + overflowedIndicator: getComponentFromProp(this, 'overflowedIndicator', props), openTransitionName: this.getOpenTransitionName(), parentMenu: this, children: this.$slots.default || [], diff --git a/components/vc-menu/MenuItem.jsx b/components/vc-menu/MenuItem.jsx index d197c2fac..2e12d011c 100644 --- a/components/vc-menu/MenuItem.jsx +++ b/components/vc-menu/MenuItem.jsx @@ -5,6 +5,7 @@ import BaseMixin from '../_util/BaseMixin' import scrollIntoView from 'dom-scroll-into-view' import { connect } from '../_util/store' import { noop, menuAllProps } from './util' +import { getComponentFromProp } from '../_util/props-util' const props = { attribute: PropTypes.object, @@ -25,6 +26,7 @@ const props = { manualRef: PropTypes.func.def(noop), role: PropTypes.any, subMenuKey: PropTypes.string, + itemIcon: PropTypes.any, // clearSubMenuTimers: PropTypes.func.def(noop), } const MenuItem = { @@ -138,7 +140,7 @@ const MenuItem = { let attrs = { ...props.attribute, title: props.title, - role: 'menuitem', + role: props.role || 'menuitem', 'aria-disabled': props.disabled, } if (props.role === 'option') { @@ -148,10 +150,13 @@ const MenuItem = { role: 'option', 'aria-selected': props.isSelected, } - } else if (props.role === null) { + } else if (props.role === null || props.role === 'none') { // sometimes we want to specify role inside
  • element //
  • Link
  • would be a good example - delete attrs.role + // in this case the role on
  • should be "none" to + // remove the implied listitem role. + // https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html + attrs.role = 'none' } // In case that onClick/onMouseLeave/onMouseEnter is passed down from owner const mouseEvent = { @@ -184,6 +189,7 @@ const MenuItem = { class={className} > {this.$slots.default} + {getComponentFromProp(this, 'itemIcon', props)}
  • ) }, diff --git a/components/vc-menu/SubMenu.jsx b/components/vc-menu/SubMenu.jsx index 275a89d02..5a6f670d2 100644 --- a/components/vc-menu/SubMenu.jsx +++ b/components/vc-menu/SubMenu.jsx @@ -66,6 +66,9 @@ const SubMenu = { store: PropTypes.object, mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']).def('vertical'), manualRef: PropTypes.func.def(noop), + builtinPlacements: PropTypes.object.def({}), + itemIcon: PropTypes.any, + expandIcon: PropTypes.any, }, mixins: [BaseMixin], isSubMenu: true, @@ -350,11 +353,14 @@ const SubMenu = { subMenuCloseDelay: props.subMenuCloseDelay, forceSubMenuRender: props.forceSubMenuRender, triggerSubMenuAction: props.triggerSubMenuAction, + builtinPlacements: props.builtinPlacements, defaultActiveFirst: props.store.getState() .defaultActiveFirst[getMenuIdFromSubMenuEventKey(props.eventKey)], multiple: props.multiple, prefixCls: props.rootPrefixCls, manualRef: this.saveMenuInstance, + itemIcon: getComponentFromProp(this, 'itemIcon'), + expandIcon: getComponentFromProp(this, 'expandIcon'), children, __propsSymbol__: Symbol(), }, @@ -475,10 +481,15 @@ const SubMenu = { class: `${prefixCls}-title`, ref: 'subMenuTitle', } + // expand custom icon should NOT be displayed in menu with horizontal mode. + let icon = null + if (props.mode !== 'horizontal') { + icon = getComponentFromProp(this, 'expandIcon', props) + } const title = (
    {getComponentFromProp(this, 'title')} - + {icon || }
    ) const children = this.renderChildren(this.$slots.default) @@ -503,6 +514,7 @@ const SubMenu = { popupClassName={`${prefixCls}-popup ${rootPrefixCls}-${parentMenu.theme} ${popupClassName || ''}`} getPopupContainer={getPopupContainer} builtinPlacements={placements} + builtinPlacements={Object.assign({}, placements, props.builtinPlacements)} popupPlacement={popupPlacement} popupVisible={isOpen} popupAlign={popupAlign} diff --git a/components/vc-menu/SubPopupMenu.jsx b/components/vc-menu/SubPopupMenu.jsx index 0109cf808..e0c54ffeb 100644 --- a/components/vc-menu/SubPopupMenu.jsx +++ b/components/vc-menu/SubPopupMenu.jsx @@ -7,7 +7,7 @@ import classNames from 'classnames' import { getKeyFromChildrenIndex, loopMenuItem, noop } from './util' import DOMWrap from './DOMWrap' import { cloneElement } from '../_util/vnode' -import { initDefaultProps, getOptionProps, getEvents } from '../_util/props-util' +import { initDefaultProps, getOptionProps, getPropsData, getEvents, getComponentFromProp } from '../_util/props-util' function allDisabled (arr) { if (!arr.length) { @@ -28,6 +28,11 @@ function updateActiveKey (store, menuId, activeKey) { }) } +function getEventKey (props) { + // when eventKey not available ,it's menu and return menu id '0-menu-' + return props.eventKey || '0-menu-' +} + export function saveRef (key, c) { if (c) { const index = this.instanceArrayKeyIndexMap[key] @@ -100,6 +105,8 @@ const SubPopupMenu = { triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']), inlineIndent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), manualRef: PropTypes.func, + itemIcon: PropTypes.any, + expandIcon: PropTypes.any, children: PropTypes.any.def([]), __propsSymbol__: PropTypes.any, // mock componentWillReceiveProps }, { @@ -129,18 +136,27 @@ const SubPopupMenu = { this.manualRef(this) } }, - watch: { - __propsSymbol__ () { - const props = getOptionProps(this) - const storeActiveKey = this.getStore().getState().activeKey[this.getEventKey()] - const originalActiveKey = 'activeKey' in props ? props.activeKey - : storeActiveKey - const activeKey = getActiveKey(props, originalActiveKey) - if (activeKey !== originalActiveKey || storeActiveKey !== activeKey) { - updateActiveKey(this.getStore(), this.getEventKey(), activeKey) - } - }, + updated () { + const props = getOptionProps(this) + const originalActiveKey = 'activeKey' in props ? props.activeKey + : props.store.getState().activeKey[getEventKey(props)] + const activeKey = getActiveKey(props, originalActiveKey) + if (activeKey !== originalActiveKey) { + updateActiveKey(props.store, getEventKey(props), activeKey) + } }, + // watch: { + // __propsSymbol__ () { + // const props = getOptionProps(this) + // const storeActiveKey = this.getStore().getState().activeKey[this.getEventKey()] + // const originalActiveKey = 'activeKey' in props ? props.activeKey + // : storeActiveKey + // const activeKey = getActiveKey(props, originalActiveKey) + // if (activeKey !== originalActiveKey || storeActiveKey !== activeKey) { + // updateActiveKey(this.getStore(), this.getEventKey(), activeKey) + // } + // }, + // }, methods: { // all keyboard events callbacks run from here at first onKeyDown (e, callback) { @@ -160,7 +176,7 @@ const SubPopupMenu = { } if (activeItem) { e.preventDefault() - updateActiveKey(this.getStore(), this.getEventKey(), activeItem.eventKey) + updateActiveKey(this.$props.store, getEventKey(this.$props), activeItem.eventKey) if (typeof callback === 'function') { callback(activeItem) @@ -172,7 +188,7 @@ const SubPopupMenu = { onItemHover (e) { const { key, hover } = e - updateActiveKey(this.getStore(), this.getEventKey(), hover ? key : null) + updateActiveKey(this.$props.store, getEventKey(this.$props), hover ? key : null) }, onDeselect (selectInfo) { @@ -199,22 +215,13 @@ const SubPopupMenu = { return this.instanceArray }, - getStore () { - return this.store - }, - - getEventKey () { - // when eventKey not available ,it's menu and return menu id '0-menu-' - return this.eventKey !== undefined ? this.eventKey : '0-menu-' - }, - getOpenTransitionName () { return this.$props.openTransitionName }, step (direction) { let children = this.getFlatInstanceArray() - const activeKey = this.getStore().getState().activeKey[this.getEventKey()] + const activeKey = this.$props.store.getState().activeKey[this.getEventKey(this.$props)] const len = children.length if (!len) { return null @@ -251,14 +258,39 @@ const SubPopupMenu = { return null }, - + getIcon (instance, name) { + if (instance.$createElement) { + const temp = instance[name] + if (temp !== undefined) { + return temp + } + return instance.$slots[name] || instance.$scopedSlots[name] + } else { + const temp = getPropsData(instance)[name] + if (temp !== undefined) { + return temp + } + const slotsProp = [] + const componentOptions = instance.componentOptions || {}; + (componentOptions.children || []).forEach((child) => { + if (child.data && child.data.slot === name) { + if (child.tag === 'template') { + slotsProp.push(child.children) + } else { + slotsProp.push(child) + } + } + }) + return slotsProp.length ? slotsProp : undefined + } + }, renderCommonMenuItem (child, i, extraProps) { if (child.tag === undefined) { return child } - const state = this.getStore().getState() + const state = this.$props.store.getState() const props = this.$props const key = getKeyFromChildrenIndex(child, props.eventKey, i) const childProps = child.componentOptions.propsData || {} - const isActive = key === state.activeKey[this.getEventKey()] + const isActive = key === state.activeKey if (!childProps.disabled) { // manualRef的执行顺序不能保证,使用key映射ref在this.instanceArray中的位置 this.instanceArrayKeyIndexMap[key] = Object.keys(this.instanceArrayKeyIndexMap).length @@ -266,7 +298,7 @@ const SubPopupMenu = { const childListeners = getEvents(child) const newChildProps = { props: { - mode: props.mode, + mode: childProps.mode || props.mode, level: props.level, inlineIndent: props.inlineIndent, renderMenuItem: this.renderMenuItem, @@ -283,6 +315,9 @@ const SubPopupMenu = { subMenuOpenDelay: props.subMenuOpenDelay, subMenuCloseDelay: props.subMenuCloseDelay, forceSubMenuRender: props.forceSubMenuRender, + builtinPlacements: props.builtinPlacements, + itemIcon: this.getIcon(child, 'itemIcon') || this.getIcon(this, 'itemIcon'), + expandIcon: this.getIcon(child, 'expandIcon') || this.getIcon(this, 'expandIcon'), ...extraProps, }, on: { @@ -307,7 +342,7 @@ const SubPopupMenu = { if (!c) { return null } - const state = this.getStore().getState() + const state = this.$props.store.getState() const extraProps = { openKeys: state.openKeys, selectedKeys: state.selectedKeys, @@ -320,7 +355,7 @@ const SubPopupMenu = { }, render () { const { ...props } = this.$props - const { eventKey, visible } = props + const { eventKey, visible, level, mode, theme } = props this.instanceArray = [] this.instanceArrayKeyIndexMap = {} const className = classNames( @@ -332,6 +367,8 @@ const SubPopupMenu = { tag: 'ul', // hiddenClassName: `${prefixCls}-hidden`, visible, + level, mode, theme, + overflowedIndicator: getComponentFromProp(this, 'overflowedIndicator'), }, attrs: { role: props.role || 'menu', diff --git a/components/vc-menu/assets/index.less b/components/vc-menu/assets/index.less index 1da996cf5..fc4a1b839 100644 --- a/components/vc-menu/assets/index.less +++ b/components/vc-menu/assets/index.less @@ -2,8 +2,8 @@ @font-face { font-family: 'FontAwesome'; - src: url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0'); - src: url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg'); + src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.eot'); + src: url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix') format('embedded-opentype'), url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.woff') format('woff'), url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf') format('truetype'), url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } @@ -92,6 +92,10 @@ &-submenu { &-popup { position: absolute; + + .submenu-title-wrapper { + padding-right: 20px; + } } > .@{menuPrefixCls} { background-color: #fff; @@ -110,17 +114,19 @@ &-horizontal { background-color: #F3F5F7; border: none; - border-bottom: 1px solid transparent; border-bottom: 1px solid #d9d9d9; box-shadow: none; + white-space: nowrap; + overflow: hidden; & > .@{menuPrefixCls}-item, & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { padding: 15px 20px; } & > .@{menuPrefixCls}-submenu, & > .@{menuPrefixCls}-item { - float: left; border-bottom: 2px solid transparent; + display: inline-block; + vertical-align: bottom; &-active { border-bottom: 2px solid #2db7f5; diff --git a/components/vc-menu/commonPropsType.js b/components/vc-menu/commonPropsType.js index f7736e4aa..02c06a026 100644 --- a/components/vc-menu/commonPropsType.js +++ b/components/vc-menu/commonPropsType.js @@ -23,4 +23,8 @@ export default { forceSubMenuRender: PropTypes.bool, selectable: PropTypes.bool, isRootMenu: PropTypes.bool.def(true), + builtinPlacements: PropTypes.object.def({}), + itemIcon: PropTypes.any, + expandIcon: PropTypes.any, + overflowedIndicator: PropTypes.any, } diff --git a/components/vc-menu/index.js b/components/vc-menu/index.js index ce70ff19d..3e6b27248 100644 --- a/components/vc-menu/index.js +++ b/components/vc-menu/index.js @@ -1,4 +1,4 @@ -// based on rc-menu 7.0.5 +// based on rc-menu 7.4.19 import Menu from './Menu' import SubMenu from './SubMenu' import MenuItem, { menuItemProps } from './MenuItem' diff --git a/components/vc-menu/util.js b/components/vc-menu/util.js index dc41512b8..55228298f 100644 --- a/components/vc-menu/util.js +++ b/components/vc-menu/util.js @@ -77,6 +77,7 @@ export const menuAllProps = { 'rootPrefixCls', 'eventKey', 'active', + 'popupAlign', 'popupOffset', 'isOpen', 'renderMenuItem', @@ -87,6 +88,8 @@ export const menuAllProps = { 'isSelected', 'store', 'activeKey', + 'builtinPlacements', + 'overflowedIndicator', // the following keys found need to be removed from test regression 'attribute', @@ -95,6 +98,8 @@ export const menuAllProps = { 'inlineCollapsed', 'menu', 'theme', + 'itemIcon', + 'expandIcon', ], on: [ 'select', @@ -107,3 +112,15 @@ export const menuAllProps = { 'titleClick', ], } + +export const getWidth = (elem) => ( + elem && + typeof elem.getBoundingClientRect === 'function' && + elem.getBoundingClientRect().width +) || 0 + +export const setStyle = (elem, styleProperty, value) => { + if (elem && typeof elem.style === 'object') { + elem.style[styleProperty] = value + } +} diff --git a/package.json b/package.json index 2f86bbc5a..fe35602bb 100644 --- a/package.json +++ b/package.json @@ -172,6 +172,7 @@ "json2mq": "^0.2.0", "lodash": "^4.17.5", "moment": "^2.21.0", + "mutationobserver-shim": "^0.3.2", "omit.js": "^1.0.0", "raf": "^3.4.0", "resize-observer-polyfill": "^1.5.0",