mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-01-18 12:34:25 +08:00
refactor(menu): get rid of dropdown in menu, instead using popover to show hidden menu
This commit is contained in:
parent
1d35e6945f
commit
45ee04bbb2
@ -28,7 +28,7 @@ function createRenderer (wrapCodeWithCard = true) {
|
||||
return `<n-p>${text}</n-p>`
|
||||
},
|
||||
link (href, title, text) {
|
||||
return `<n-a title="${title}" href="${href}">${text}</n-a>`
|
||||
return `<n-a to="${href}" >${text}</n-a>`
|
||||
},
|
||||
list (body, ordered, start) {
|
||||
const type = ordered ? 'n-ol' : 'n-ul'
|
||||
|
@ -21,7 +21,8 @@ export default {
|
||||
name: 'Menu',
|
||||
provide () {
|
||||
return {
|
||||
NMenu: this
|
||||
NMenu: this,
|
||||
NSubmenu: null
|
||||
}
|
||||
},
|
||||
mixins: [withapp, themeable],
|
||||
@ -65,6 +66,10 @@ export default {
|
||||
openNames: {
|
||||
type: Array,
|
||||
default: undefined
|
||||
},
|
||||
inPopover: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
@ -83,7 +88,7 @@ export default {
|
||||
this.$emit('select', value)
|
||||
this.$emit('input', value)
|
||||
},
|
||||
handleOpenNamesChange (name) {
|
||||
toggleOpenName (name) {
|
||||
const currentOpenNames = Array.from(this.synthesizedOpenNames)
|
||||
const index = currentOpenNames.findIndex(openName => openName === name)
|
||||
if (~index) {
|
||||
@ -94,7 +99,13 @@ export default {
|
||||
if (this.openNames === undefined) {
|
||||
this.internalOpenNames = currentOpenNames
|
||||
}
|
||||
this.$emit('openNamesChange', currentOpenNames)
|
||||
this.$emit('open-names-change', currentOpenNames)
|
||||
},
|
||||
handleOpenNamesChange (names) {
|
||||
if (this.openName === undefined) {
|
||||
this.internalOpenNames = names
|
||||
}
|
||||
this.$emit('open-names-change', names)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +1,5 @@
|
||||
<template>
|
||||
<li
|
||||
v-if="!shouldBeRenderedAsDropdownItem && isFirstLevel"
|
||||
:key="name"
|
||||
class="n-menu-item-wrapper"
|
||||
>
|
||||
<n-tooltip trigger="hover" :disabled="!NMenu.collapsed" placement="right" :delay="300">
|
||||
<template v-slot:activator>
|
||||
<div
|
||||
class="n-menu-item"
|
||||
:style="{ paddingLeft: delayedPaddingLeft + 'px' }"
|
||||
:class="{
|
||||
'n-menu-item--selected': selected,
|
||||
'n-menu-item--disabled': synthesizedDisabled
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div
|
||||
v-if="$slots.icon"
|
||||
class="n-menu-item__icon"
|
||||
:style="{
|
||||
width: maxIconSize && (maxIconSize + 'px'),
|
||||
height: maxIconSize && (maxIconSize + 'px'),
|
||||
fontSize: activeIconSize && (activeIconSize + 'px'),
|
||||
}"
|
||||
>
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<div class="n-menu-item__header">
|
||||
<slot>
|
||||
<render :render="title" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<render :render="title" />
|
||||
</n-tooltip>
|
||||
</li>
|
||||
<li
|
||||
v-else-if="!shouldBeRenderedAsDropdownItem"
|
||||
:key="name"
|
||||
class="n-menu-item"
|
||||
:style="{ paddingLeft: delayedPaddingLeft + 'px' }"
|
||||
@ -47,26 +9,23 @@
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<!-- identical part start -->
|
||||
<div
|
||||
v-if="$slots.icon"
|
||||
class="n-menu-item__icon"
|
||||
:style="{
|
||||
width: iconSize && (iconSize + 'px'),
|
||||
height: iconSize && (iconSize + 'px'),
|
||||
fontSize: iconSize && (iconSize + 'px'),
|
||||
width: maxIconSize && (maxIconSize + 'px'),
|
||||
height: maxIconSize && (maxIconSize + 'px'),
|
||||
fontSize: activeIconSize && (activeIconSize + 'px'),
|
||||
}"
|
||||
>
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<!-- identical part start end -->
|
||||
<div class="n-menu-item__header">
|
||||
<slot>
|
||||
<render :render="title" />
|
||||
</slot>
|
||||
</div>
|
||||
</li>
|
||||
<n-dropdown-item v-else :name="name" :label="title" :value="value" :selected="selected" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -74,18 +33,16 @@ import collectable from '../../../mixins/collectable'
|
||||
import withapp from '../../../mixins/withapp'
|
||||
import themeable from '../../../mixins/themeable'
|
||||
import render from '../../../utils/render'
|
||||
import NTooltip from '../../Tooltip'
|
||||
import NDropdownItem from '../../Dropdown/src/DropdownItem'
|
||||
|
||||
export default {
|
||||
name: 'NMenuItem',
|
||||
components: {
|
||||
NTooltip,
|
||||
NDropdownItem,
|
||||
render
|
||||
},
|
||||
mixins: [
|
||||
collectable('NSubmenu', 'menuItemNames', 'name', true),
|
||||
collectable('NSubmenu', 'menuItemNames', 'name', true, function (injection) {
|
||||
return this.NMenu !== injection.NMenu
|
||||
}),
|
||||
withapp,
|
||||
themeable
|
||||
],
|
||||
@ -98,9 +55,6 @@ export default {
|
||||
},
|
||||
NMenuItemGroup: {
|
||||
default: null
|
||||
},
|
||||
NMenuUl: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@ -127,13 +81,6 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
disabledCollectable () {
|
||||
return !this.NMenuUl
|
||||
},
|
||||
shouldBeRenderedAsDropdownItem () {
|
||||
if (this.NMenuUl) return false
|
||||
return !this.isFirstLevel && this.NMenu.collapsed
|
||||
},
|
||||
useCollapsedIconSize () {
|
||||
return this.NMenu.collapsed && this.isFirstLevel
|
||||
},
|
||||
|
@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<ul>
|
||||
<slot />
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NMenuUl',
|
||||
provide () {
|
||||
return {
|
||||
NMenuUl: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,117 +1,116 @@
|
||||
<template>
|
||||
<n-dropdown-submenu
|
||||
v-if="shouldBeRenderedAsDropdownSubmenu && !NMenuUl"
|
||||
:value="value"
|
||||
:label="title"
|
||||
:name="name"
|
||||
:selected="selectedInside"
|
||||
>
|
||||
<template v-slot:activator>
|
||||
<render :render="title" />
|
||||
</template>
|
||||
<slot />
|
||||
</n-dropdown-submenu>
|
||||
<li
|
||||
v-else
|
||||
class="n-submenu"
|
||||
:class="{
|
||||
'n-submenu--selected-inside': selectedInside
|
||||
}"
|
||||
>
|
||||
<n-dropdown
|
||||
v-if="isFirstLevel"
|
||||
size="large"
|
||||
trigger="click"
|
||||
:focusable="false"
|
||||
:disabled="!NMenu.collapsed"
|
||||
placement="right-start"
|
||||
type="menu"
|
||||
@select="handleDropdownSelect"
|
||||
>
|
||||
<template v-slot:activator>
|
||||
<div
|
||||
class="n-submenu-item n-dropdown"
|
||||
:style="{ paddingLeft: delayedPaddingLeft + 'px' }"
|
||||
:class="{
|
||||
'n-submenu-item--collapsed': contentCollapsed,
|
||||
'n-submenu-item--active': !contentCollapsed,
|
||||
'n-submenu-item--disabled': disabled
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<template v-if="isFirstLevel">
|
||||
<n-popover trigger="click" placement="right-start" :disabled="!usePopover">
|
||||
<template v-slot:activator>
|
||||
<div
|
||||
v-if="$slots.icon"
|
||||
class="n-submenu-item__icon"
|
||||
:style="{
|
||||
width: maxIconSize && (maxIconSize + 'px'),
|
||||
height: maxIconSize && (maxIconSize + 'px'),
|
||||
fontSize: activeIconSize && (activeIconSize + 'px'),
|
||||
class="n-submenu-item"
|
||||
:style="{ paddingLeft: delayedPaddingLeft + 'px' }"
|
||||
:class="{
|
||||
'n-submenu-item--collapsed': synthesizedCollapsed,
|
||||
'n-submenu-item--active': !synthesizedCollapsed,
|
||||
'n-submenu-item--disabled': disabled
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<slot name="icon" />
|
||||
<div
|
||||
v-if="$slots.icon"
|
||||
class="n-submenu-item__icon"
|
||||
:style="{
|
||||
width: maxIconSize && (maxIconSize + 'px'),
|
||||
height: maxIconSize && (maxIconSize + 'px'),
|
||||
fontSize: activeIconSize && (activeIconSize + 'px'),
|
||||
}"
|
||||
>
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<div class="n-submenu-item__header">
|
||||
<slot name="header">
|
||||
<render :render="title" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="n-submenu-item__header">
|
||||
<slot name="header">
|
||||
<render :render="title" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<slot />
|
||||
</n-dropdown>
|
||||
<div
|
||||
v-else
|
||||
class="n-submenu-item n-submenu-item--as-dropdown"
|
||||
:style="{ paddingLeft: delayedPaddingLeft + 'px' }"
|
||||
:class="{
|
||||
'n-submenu-item--collapsed': contentCollapsed,
|
||||
'n-submenu-item--active': !contentCollapsed,
|
||||
'n-submenu-item--disabled': disabled
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
</template>
|
||||
<n-menu
|
||||
:style="{
|
||||
width: '272px'
|
||||
}"
|
||||
:root-indent="24"
|
||||
in-popover
|
||||
:value="popMenuValue"
|
||||
:open-names="popMenuOpenNames"
|
||||
@select="handlePopMenuSelect"
|
||||
@open-names-change="handlePopMenuOpenNamesChange"
|
||||
>
|
||||
<slot />
|
||||
</n-menu>
|
||||
</n-popover>
|
||||
<fade-in-height-expand-transition>
|
||||
<ul
|
||||
v-show="!synthesizedCollapsed"
|
||||
class="n-submenu-content"
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
</fade-in-height-expand-transition>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="$slots.icon"
|
||||
class="n-submenu-item__icon"
|
||||
:style="{
|
||||
width: iconSize && (iconSize + 'px'),
|
||||
height: iconSize && (iconSize + 'px'),
|
||||
fontSize: iconSize && (iconSize + 'px'),
|
||||
class="n-submenu-item"
|
||||
:style="{ paddingLeft: delayedPaddingLeft + 'px' }"
|
||||
:class="{
|
||||
'n-submenu-item--collapsed': synthesizedCollapsed,
|
||||
'n-submenu-item--active': !synthesizedCollapsed,
|
||||
'n-submenu-item--disabled': disabled
|
||||
}"
|
||||
@click="handleClick"
|
||||
>
|
||||
<slot name="icon" />
|
||||
<div
|
||||
v-if="$slots.icon"
|
||||
class="n-submenu-item__icon"
|
||||
:style="{
|
||||
width: iconSize && (iconSize + 'px'),
|
||||
height: iconSize && (iconSize + 'px'),
|
||||
fontSize: iconSize && (iconSize + 'px'),
|
||||
}"
|
||||
>
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<div class="n-submenu-item__header">
|
||||
<slot name="header">
|
||||
<render :render="title" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="n-submenu-item__header">
|
||||
<slot name="header">
|
||||
<render :render="title" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<fade-in-height-expand-transition>
|
||||
<n-menu-ul
|
||||
v-show="!contentCollapsed"
|
||||
class="n-submenu-content"
|
||||
>
|
||||
<slot />
|
||||
</n-menu-ul>
|
||||
</fade-in-height-expand-transition>
|
||||
<fade-in-height-expand-transition>
|
||||
<ul
|
||||
v-show="!synthesizedCollapsed"
|
||||
class="n-submenu-content"
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
</fade-in-height-expand-transition>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FadeInHeightExpandTransition from '../../../transition/FadeInHeightExpandTransition'
|
||||
import render from '../../../utils/render'
|
||||
import NDropdown from '../../Dropdown/src/Dropdown'
|
||||
import NDropdownSubmenu from '../../Dropdown/src/DropdownSubmenu'
|
||||
import NMenuUl from './MenuUl'
|
||||
import NPopover from '../../../common/Popover'
|
||||
import NMenu from './Menu'
|
||||
|
||||
export default {
|
||||
name: 'NSubmenu',
|
||||
components: {
|
||||
FadeInHeightExpandTransition,
|
||||
NDropdown,
|
||||
NDropdownSubmenu,
|
||||
NMenuUl,
|
||||
NPopover,
|
||||
NMenu,
|
||||
render
|
||||
},
|
||||
props: {
|
||||
@ -142,8 +141,8 @@ export default {
|
||||
selectedInside () {
|
||||
return this.menuItemNames.includes(this.NMenu.value)
|
||||
},
|
||||
shouldBeRenderedAsDropdownSubmenu () {
|
||||
return this.NMenu.collapsed && !this.isFirstLevel
|
||||
usePopover () {
|
||||
return this.useCollapsedIconSize
|
||||
},
|
||||
useCollapsedIconSize () {
|
||||
return this.NMenu.collapsed && this.isFirstLevel
|
||||
@ -182,8 +181,25 @@ export default {
|
||||
return this.NMenu.rootIndent || this.NMenu.indent
|
||||
}
|
||||
},
|
||||
contentCollapsed () {
|
||||
return this.NMenu.collapsed || !(this.NMenu.synthesizedOpenNames.includes(this.name))
|
||||
rootMenuCollapsed () {
|
||||
return this.NMenu.collapsed
|
||||
},
|
||||
rootMenuInPopover () {
|
||||
return this.NMenu.inPopover
|
||||
},
|
||||
selfCollapsed () {
|
||||
return !this.NMenu.synthesizedOpenNames.includes(this.name)
|
||||
},
|
||||
synthesizedCollapsed () {
|
||||
if (this.rootMenuInPopover) return this.selfCollapsed
|
||||
else if (this.rootMenuCollapsed) return true
|
||||
return this.selfCollapsed
|
||||
},
|
||||
popMenuValue () {
|
||||
return this.NMenu.value
|
||||
},
|
||||
popMenuOpenNames () {
|
||||
return this.NMenu.synthesizedOpenNames
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -211,24 +227,23 @@ export default {
|
||||
},
|
||||
NMenuItemGroup: {
|
||||
default: null
|
||||
},
|
||||
NMenuUl: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// console.log('submenu created', this.name)
|
||||
this.delayedPaddingLeft = this.paddingLeft
|
||||
},
|
||||
methods: {
|
||||
handleDropdownSelect (value) {
|
||||
this.NMenu.handleSelect(value)
|
||||
},
|
||||
handleClick () {
|
||||
if (!this.disabled && !this.NMenu.collapsed) {
|
||||
this.NMenu.handleOpenNamesChange(this.name)
|
||||
this.NMenu.toggleOpenName(this.name)
|
||||
this.$emit('click', this)
|
||||
}
|
||||
},
|
||||
handlePopMenuSelect (value) {
|
||||
this.NMenu.handleSelect(value)
|
||||
},
|
||||
handlePopMenuOpenNamesChange (value) {
|
||||
this.NMenu.handleOpenNamesChange(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ export default function (
|
||||
injectionName,
|
||||
collectionProperty,
|
||||
registerProperty = 'value',
|
||||
bubble = false
|
||||
bubble = false,
|
||||
disabledCollectable = null
|
||||
) {
|
||||
const registerPropertyChangeHandler = function (value, oldValue) {
|
||||
if (this.activeCollectableInjection) {
|
||||
@ -38,20 +39,20 @@ export default function (
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
console.log('before destroy', this.name, this.$el)
|
||||
// console.log('before destroy', this.name, this.$el)
|
||||
if (this.activeCollectableInjection) {
|
||||
this.registerValue(undefined, this[registerProperty])
|
||||
}
|
||||
},
|
||||
destroyed () {
|
||||
console.log('destroyed', this.name)
|
||||
// console.log('destroyed', this.name)
|
||||
},
|
||||
methods: {
|
||||
registerValue (value = undefined, oldValue = undefined) {
|
||||
if (this.disabledCollectable) return
|
||||
console.log('registerValue')
|
||||
// console.log('registerValue')
|
||||
let currentInjection = this.activeCollectableInjection
|
||||
while (currentInjection) {
|
||||
if (disabledCollectable && disabledCollectable.call(this, currentInjection)) return
|
||||
const collectedValues = currentInjection[collectionProperty]
|
||||
if (oldValue !== undefined) {
|
||||
const oldValueIndex = collectedValues.findIndex(collectedValue => collectedValue === oldValue)
|
||||
|
Loading…
Reference in New Issue
Block a user