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