ant-design-vue/components/menu/index.jsx

266 lines
8.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import omit from 'omit.js'
import VcMenu, { Divider, ItemGroup, SubMenu } from '../vc-menu'
import PropTypes from '../_util/vue-types'
import animation from '../_util/openAnimation'
import warning from '../_util/warning'
import Item from './MenuItem'
import { hasProp } from '../_util/props-util'
import BaseMixin from '../_util/BaseMixin'
import commonPropsType from '../vc-menu/commonPropsType'
export const MenuMode = PropTypes.oneOf(['vertical', 'vertical-left', 'vertical-right', 'horizontal', 'inline'])
export const menuProps = {
...commonPropsType,
theme: PropTypes.oneOf(['light', 'dark']).def('light'),
mode: MenuMode.def('vertical'),
selectable: PropTypes.bool,
selectedKeys: PropTypes.arrayOf(PropTypes.string),
defaultSelectedKeys: PropTypes.array,
openKeys: PropTypes.array,
defaultOpenKeys: PropTypes.array,
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
openTransitionName: PropTypes.string,
prefixCls: PropTypes.string.def('ant-menu'),
multiple: PropTypes.bool,
inlineIndent: PropTypes.number.def(24),
inlineCollapsed: PropTypes.bool,
isRootMenu: PropTypes.bool.def(true),
focusable: PropTypes.bool.def(false),
}
const Menu = {
name: 'AMenu',
props: menuProps,
Divider: { ...Divider, name: 'AMenuDivider' },
Item: { ...Item, name: 'AMenuItem' },
SubMenu: { ...SubMenu, name: 'ASubMenu' },
ItemGroup: { ...ItemGroup, name: 'AMenuItemGroup' },
provide () {
return {
getInlineCollapsed: this.getInlineCollapsed,
}
},
mixins: [BaseMixin],
inject: {
layoutSiderContext: { default: {}},
configProvider: { default: {}},
},
model: {
prop: 'selectedKeys',
event: 'selectChange',
},
created () {
this.preProps = { ...this.$props }
},
updated () {
this.propsUpdating = false
},
watch: {
mode (val, oldVal) {
if (oldVal === 'inline' && val !== 'inline') {
this.switchingModeFromInline = true
}
},
openKeys (val) {
this.setState({ sOpenKeys: val })
},
inlineCollapsed (val) {
this.collapsedChange(val)
},
'layoutSiderContext.sCollapsed': function (val) {
this.collapsedChange(val)
},
},
data () {
const props = this.$props
warning(
!(hasProp(this, 'inlineCollapsed') && props.mode !== 'inline'),
'`inlineCollapsed` should only be used when Menu\'s `mode` is inline.',
)
this.switchingModeFromInline = false
this.leaveAnimationExecutedWhenInlineCollapsed = false
this.inlineOpenKeys = []
let sOpenKeys
if (hasProp(this, 'openKeys')) {
sOpenKeys = props.openKeys
} else if (hasProp(this, 'defaultOpenKeys')) {
sOpenKeys = props.defaultOpenKeys
}
return {
sOpenKeys,
}
},
methods: {
collapsedChange (val) {
if (this.propsUpdating) { return }
this.propsUpdating = true
if (!hasProp(this, 'openKeys')) {
if (val) {
this.switchingModeFromInline = true
this.inlineOpenKeys = this.sOpenKeys
this.setState({ sOpenKeys: [] })
} else {
this.setState({ sOpenKeys: this.inlineOpenKeys })
this.inlineOpenKeys = []
}
} else if (val) {
// 缩起时openKeys置为空的动画会闪动react可以通过是否传递openKeys避免闪动vue不是很方便动态传递openKeys
this.switchingModeFromInline = true
}
},
restoreModeVerticalFromInline () {
if (this.switchingModeFromInline) {
this.switchingModeFromInline = false
this.$forceUpdate()
}
},
// Restore vertical mode when menu is collapsed responsively when mounted
// https://github.com/ant-design/ant-design/issues/13104
// TODO: not a perfect solution, looking a new way to avoid setting switchingModeFromInline in this situation
handleMouseEnter (e) {
this.restoreModeVerticalFromInline()
this.$emit('mouseenter', e)
},
handleTransitionEnd (e) {
// console.log(111)
// when inlineCollapsed menu width animation finished
// https://github.com/ant-design/ant-design/issues/12864
const widthCollapsed = e.propertyName === 'width' && e.target === e.currentTarget
// Fix for <Menu style={{ width: '100%' }} />, the width transition won't trigger when menu is collapsed
// https://github.com/ant-design/ant-design-pro/issues/2783
const iconScaled = e.propertyName === 'font-size' && e.target.className.indexOf('anticon') >= 0
if (widthCollapsed || iconScaled) {
this.restoreModeVerticalFromInline()
}
},
handleClick (e) {
this.handleOpenChange([])
this.$emit('click', e)
},
handleSelect (info) {
this.$emit('select', info)
this.$emit('selectChange', info.selectedKeys)
},
handleDeselect (info) {
this.$emit('deselect', info)
this.$emit('selectChange', info.selectedKeys)
},
handleOpenChange (openKeys) {
this.setOpenKeys(openKeys)
this.$emit('openChange', openKeys)
this.$emit('update:openKeys', openKeys)
},
setOpenKeys (openKeys) {
if (!hasProp(this, 'openKeys')) {
this.setState({ sOpenKeys: openKeys })
}
},
getRealMenuMode () {
const inlineCollapsed = this.getInlineCollapsed()
if (this.switchingModeFromInline && inlineCollapsed) {
return 'inline'
}
const { mode } = this.$props
return inlineCollapsed ? 'vertical' : mode
},
getInlineCollapsed () {
const { inlineCollapsed } = this.$props
if (this.layoutSiderContext.sCollapsed !== undefined) {
return this.layoutSiderContext.sCollapsed
}
return inlineCollapsed
},
getMenuOpenAnimation (menuMode) {
const { openAnimation, openTransitionName } = this.$props
let menuOpenAnimation = openAnimation || openTransitionName
if (openAnimation === undefined && openTransitionName === undefined) {
if (menuMode === 'horizontal') {
menuOpenAnimation = 'slide-up'
} else if (menuMode === 'inline') {
menuOpenAnimation = { on: animation }
} else {
// When mode switch from inline
// submenu should hide without animation
if (this.switchingModeFromInline) {
menuOpenAnimation = ''
this.switchingModeFromInline = false
} else {
menuOpenAnimation = 'zoom-big'
}
}
}
return menuOpenAnimation
},
},
render () {
const { layoutSiderContext, $slots, $listeners } = this
const { collapsedWidth } = layoutSiderContext
const { getPopupContainer: getContextPopupContainer } = this.configProvider
const { prefixCls, theme, getPopupContainer } = this.$props
const menuMode = this.getRealMenuMode()
const menuOpenAnimation = this.getMenuOpenAnimation(menuMode)
const menuClassName = {
[`${prefixCls}-${theme}`]: true,
[`${prefixCls}-inline-collapsed`]: this.getInlineCollapsed(),
}
const menuProps = {
props: {
...omit(this.$props, ['inlineCollapsed']),
getPopupContainer: getPopupContainer || getContextPopupContainer,
openKeys: this.sOpenKeys,
mode: menuMode,
},
on: {
...$listeners,
select: this.handleSelect,
deselect: this.handleDeselect,
openChange: this.handleOpenChange,
onMouseenter: this.handleMouseEnter,
},
nativeOn: {
transitionend: this.handleTransitionEnd,
},
}
if (!hasProp(this, 'selectedKeys')) {
delete menuProps.props.selectedKeys
}
if (menuMode !== 'inline') {
// closing vertical popup submenu after click it
menuProps.on.click = this.handleClick
menuProps.props.openTransitionName = menuOpenAnimation
} else {
menuProps.on.click = (e) => {
this.$emit('click', e)
}
menuProps.props.openAnimation = menuOpenAnimation
}
// https://github.com/ant-design/ant-design/issues/8587
if (
this.getInlineCollapsed() &&
(collapsedWidth === 0 || collapsedWidth === '0' || collapsedWidth === '0px')
) {
return null
}
return <VcMenu {...menuProps} class={menuClassName}>{$slots.default}</VcMenu>
},
}
/* istanbul ignore next */
Menu.install = function (Vue) {
Vue.component(Menu.name, Menu)
Vue.component(Menu.Item.name, Menu.Item)
Vue.component(Menu.SubMenu.name, Menu.SubMenu)
Vue.component(Menu.Divider.name, Menu.Divider)
Vue.component(Menu.ItemGroup.name, Menu.ItemGroup)
}
export default Menu