veui
Version:
Baidu Enterprise UI for Vue.js.
182 lines (171 loc) • 5.06 kB
JavaScript
import Icon from '../Icon'
import Link from '../Link'
import Overlay from '../Overlay'
import OptionGroup from '../OptionGroup'
import overlay from '../../mixins/overlay'
import ui from '../../mixins/ui'
import prefix from '../../mixins/prefix'
import useControllable from '../../mixins/controllable'
import outside from '../../directives/outside'
import { find } from '../../utils/datasource'
import { map, endsWith, pick, isString } from 'lodash'
const ensureSlash = (str) => (endsWith(str, '/') ? str : `${str}/`)
export default {
components: {
'veui-link': Link,
'veui-icon': Icon,
'veui-overlay': Overlay,
'veui-option-group': OptionGroup
},
uiTypes: ['select'],
directives: { outside },
props: {
active: String,
items: {
type: Array,
default () {
return []
}
},
matches: {
type: Function,
default (route, item) {
return ensureSlash(route.path) === ensureSlash(item.path)
}
}
},
computed: {
normalizedItems () {
return this.normalizeItems(this.items)
},
exactAndActiveItems () {
return this.findActiveItems(this.normalizedItems)
},
exactActiveItem () {
return this.exactAndActiveItems ? this.exactAndActiveItems[0] : null
},
activeItems () {
return this.exactAndActiveItems ? this.exactAndActiveItems.slice(1) : []
}
},
mixins: [prefix, overlay, ui, useControllable(['active'])],
created () {
if (this.$router) {
const updateActive = (route) => {
let exactActiveItem = find(this.normalizedItems, (item) =>
this.matches(route, item)
)
this.commit('active', exactActiveItem ? exactActiveItem.name : null)
}
this.$watch('$route', updateActive)
// active 受控了,初始当前路由就不同步了
if (!this.isControlled('active')) {
updateActive(this.$route)
}
}
},
methods: {
normalizeItems (items, level = '') {
let firstTabable = null
return map(items, (item, index) => {
let { to, name, children } = item
item = { ...item }
// path 是方便 matches 匹配
if (to == null) {
item.path = null
} else if (this.$router) {
let { path } = this.$router.resolve(to).route
item.path = path
} else if (isString(to)) {
item.path = to
} else {
throw new Error(
'[veui-menu] Non-string `to` cannot be resolved without Vue Router.'
)
}
index = `${level}${index}`
if (!name) {
item.name = item.path || index
}
item.value = item.value || item.name
if (!level) {
item.tabIndex = item.disabled ? null : firstTabable ? '-1' : '0'
firstTabable = item.tabIndex === '0' ? item : firstTabable
}
if (children) {
item.children = this.normalizeItems(children, `${index}-`)
}
return typeof this.postNormalize === 'function'
? this.postNormalize(item)
: item
})
},
findActiveItems (items) {
let result = []
items.some((item) => {
let exactActive = this.realActive === item.name
if (exactActive) {
result.push(item)
return true
}
if (item.children) {
let children = this.findActiveItems(item.children)
if (children) {
result = [...children, item]
return true
}
}
return false
})
return result.length ? result : null
},
pickLinkProps (data) {
return pick(data, Object.keys(Link.props))
},
activateItem (nameOrItem, closePopout) {
let item =
typeof nameOrItem === 'string'
? find(this.normalizedItems, (item) => item.name === nameOrItem)
: nameOrItem
let { to, disabled, name } = item
if (disabled) return
if (to) {
this.commit('active', name)
this.$emit('activate', item)
if (closePopout && typeof this.close === 'function') {
this.close()
}
}
},
handleSelect (nameOrItem) {
return this.activateItem(nameOrItem, true)
},
handleGroupLabelClick (group, closeMenu) {
this.activateItem(group, true)
if (group.to) {
closeMenu()
}
this.$emit('click', group)
},
// keyboard
navigate (current, items, forward, updateTabIndex, hitBoundary = false) {
items = [...items] // 兼容类数组
if (updateTabIndex) current.tabIndex = -1
let index = items.indexOf(current)
let next
if (hitBoundary) {
next = items[forward ? items.length - 1 : 0]
next.tabIndex = 0
next.focus()
return
}
let targetIndex =
index === -1
? 0
: ((forward ? index + 1 : index - 1) + items.length) % items.length
next = items[targetIndex]
if (updateTabIndex) next.tabIndex = 0
next.focus()
}
}
}