mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-02-17 13:20:52 +08:00
refactor(select): use treemate
This commit is contained in:
parent
0861ef3bf1
commit
8f41aab269
@ -30,7 +30,8 @@ const options = [
|
||||
key: 'daisy buchanan'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
type: 'divider',
|
||||
key: 'd1'
|
||||
},
|
||||
{
|
||||
label: 'Nick Carraway',
|
||||
|
@ -32,6 +32,7 @@ For other props, see [Popover Props](n-popover#Props). Note that `arrow`, `raw`
|
||||
|Property|Type|Description|
|
||||
|-|-|-|
|
||||
|type|`'divider'`||
|
||||
|key|`string \| number`|Should be unique|
|
||||
|
||||
### DropdownSubmenu Type
|
||||
|Property|Type|Description|
|
||||
|
@ -25,7 +25,8 @@ const options = [
|
||||
key: 'daisy buchanan'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
type: 'divider',
|
||||
key: 'd1'
|
||||
},
|
||||
{
|
||||
label: 'Nick Carraway',
|
||||
|
@ -51,7 +51,8 @@ const options = [
|
||||
key: 'daisy buchanan'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
type: 'divider',
|
||||
key: 'd1'
|
||||
},
|
||||
{
|
||||
label: 'Nick Carraway',
|
||||
|
@ -30,7 +30,8 @@ const options = [
|
||||
key: 'daisy buchanan'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
type: 'divider',
|
||||
key: 'd1'
|
||||
},
|
||||
{
|
||||
label: '尼克·卡拉威',
|
||||
|
@ -32,6 +32,7 @@ manual-position
|
||||
|属性|类型|说明|
|
||||
|-|-|-|
|
||||
|type|`'divider'`||
|
||||
|key|`string \| number`|需要唯一|
|
||||
|
||||
### DropdownSubmenu Type
|
||||
|属性|类型|说明|
|
||||
|
@ -27,7 +27,8 @@ const options = [
|
||||
key: 'daisy buchanan'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
type: 'divider',
|
||||
key: 'd1'
|
||||
},
|
||||
{
|
||||
label: '尼克·卡拉威',
|
||||
|
@ -50,7 +50,8 @@ const options = [
|
||||
key: 'daisy buchanan'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
type: 'divider',
|
||||
key: 'd1'
|
||||
},
|
||||
{
|
||||
label: '尼克·卡拉威',
|
||||
|
@ -131,7 +131,7 @@
|
||||
"highlight.js": "^9.18.1",
|
||||
"lodash-es": "^4.17.15",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"treemate": "^0.1.6",
|
||||
"treemate": "^0.1.7",
|
||||
"vooks": "0.0.1-alpha.1",
|
||||
"vue": "^3.0.2",
|
||||
"vue-runtime-helpers": "^1.1.2",
|
||||
|
@ -3,14 +3,14 @@ import { h } from 'vue'
|
||||
export default {
|
||||
name: 'NBaseSelectGroupHeader',
|
||||
props: {
|
||||
data: {
|
||||
tmNode: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const data = this.data
|
||||
const children = data.render ? [data.render(h, data)] : [ data.name ]
|
||||
const { tmNode: { rawNode } } = this
|
||||
const children = rawNode.render ? [rawNode.render(rawNode)] : [ rawNode.name ]
|
||||
return h('div', {
|
||||
class: 'n-base-select-group-header'
|
||||
}, children)
|
||||
|
@ -17,30 +17,28 @@
|
||||
:scrollable="scrollable"
|
||||
:container="virtualListContainer"
|
||||
:content="virtualListContent"
|
||||
@scroll="handleMenuScroll"
|
||||
@scroll="doScroll"
|
||||
>
|
||||
<virtual-list
|
||||
v-if="virtualScroll"
|
||||
ref="virtualListRef"
|
||||
class="n-virtual-list"
|
||||
:items="flattenedOptions"
|
||||
:items="tmNodes"
|
||||
:item-size="itemSize"
|
||||
:show-scrollbar="false"
|
||||
@resize="handleListResize"
|
||||
@scroll="handleListScroll"
|
||||
>
|
||||
<template v-slot="{ item: option }">
|
||||
<n-select-option
|
||||
v-if="option.type === OPTION_TYPE.OPTION"
|
||||
:key="option.key"
|
||||
:index="option.index"
|
||||
:wrapped-option="option"
|
||||
:grouped="option.grouped"
|
||||
/>
|
||||
<template v-slot="{ item: tmNode }">
|
||||
<n-select-group-header
|
||||
v-else-if="option.type === OPTION_TYPE.GROUP_HEADER"
|
||||
:key="option.key"
|
||||
:data="option.data"
|
||||
v-if="tmNode.rawNode.type === 'group'"
|
||||
:key="tmNode.key"
|
||||
:tm-node="tmNode"
|
||||
/>
|
||||
<n-select-option
|
||||
v-else
|
||||
:key="tmNode.key"
|
||||
:tm-node="tmNode"
|
||||
/>
|
||||
</template>
|
||||
</virtual-list>
|
||||
@ -48,23 +46,16 @@
|
||||
v-else
|
||||
class="n-base-select-menu-option-wrapper"
|
||||
>
|
||||
<template v-for="option in flattenedOptions">
|
||||
<n-select-option
|
||||
v-if="option.type === OPTION_TYPE.OPTION"
|
||||
:key="option.key"
|
||||
:index="option.index"
|
||||
:wrapped-option="option"
|
||||
:grouped="option.grouped"
|
||||
/>
|
||||
<template v-for="tmNode in tmNodes">
|
||||
<n-select-group-header
|
||||
v-else-if="option.type === OPTION_TYPE.GROUP_HEADER"
|
||||
:key="option.key"
|
||||
:data="option.data"
|
||||
v-if="tmNode.rawNode.type === 'group'"
|
||||
:key="tmNode.key"
|
||||
:tm-node="tmNode"
|
||||
/>
|
||||
<render
|
||||
v-else-if="option.type === OPTION_TYPE.RENDER"
|
||||
:key="option.key"
|
||||
:render="option.render"
|
||||
<n-select-option
|
||||
v-else
|
||||
:key="tmNode.key"
|
||||
:tm-node="tmNode"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
@ -84,19 +75,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { VirtualList } from 'vueuc'
|
||||
import NScrollbar from '../../../scrollbar'
|
||||
import NSelectOption from './SelectOption.js'
|
||||
import NSelectGroupHeader from './SelectGroupHeader.js'
|
||||
import NEmpty from '../../../empty'
|
||||
import { render } from '../../../_utils/vue'
|
||||
import {
|
||||
getPrevAvailableIndex,
|
||||
getNextAvailableIndex,
|
||||
flattenOptions,
|
||||
OPTION_TYPE
|
||||
} from '../../../_utils/component/select'
|
||||
import { depx, formatLength } from '../../../_utils/css'
|
||||
import { createKey } from '../../../_utils/cssr'
|
||||
import { usecssr } from '../../../_mixins'
|
||||
@ -114,8 +98,7 @@ export default {
|
||||
NScrollbar,
|
||||
NSelectOption,
|
||||
NEmpty,
|
||||
NSelectGroupHeader,
|
||||
render
|
||||
NSelectGroupHeader
|
||||
},
|
||||
mixins: [
|
||||
usecssr(styles, {
|
||||
@ -126,15 +109,15 @@ export default {
|
||||
props: {
|
||||
theme: {
|
||||
type: String,
|
||||
default: null
|
||||
default: undefined
|
||||
},
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: null
|
||||
treeMate: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
@ -142,11 +125,11 @@ export default {
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
default: 'medium'
|
||||
},
|
||||
pattern: {
|
||||
type: String,
|
||||
default: null
|
||||
default: undefined
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Array],
|
||||
@ -154,7 +137,7 @@ export default {
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: null
|
||||
default: undefined
|
||||
},
|
||||
autoPendingFirstOption: {
|
||||
type: Boolean,
|
||||
@ -172,14 +155,9 @@ export default {
|
||||
onMenuScroll: {
|
||||
type: Function,
|
||||
default: undefined
|
||||
},
|
||||
// deprecated
|
||||
emitOption: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup () {
|
||||
setup (props) {
|
||||
const virtualListRef = ref(null)
|
||||
const scrollbarRef = ref(null)
|
||||
onMounted(() => {
|
||||
@ -189,6 +167,7 @@ export default {
|
||||
if (value) value.sync()
|
||||
})
|
||||
return {
|
||||
tmNodes: computed(() => props.treeMate.flattenedNodes),
|
||||
virtualListRef,
|
||||
scrollbarRef,
|
||||
virtualListContainer () {
|
||||
@ -202,21 +181,12 @@ export default {
|
||||
value
|
||||
} = virtualListRef
|
||||
return value && value.itemsRef
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const flattenedOptions = flattenOptions(this.options)
|
||||
const firstAvailableOptionIndex = this.autoPendingFirstOption
|
||||
? getNextAvailableIndex(flattenedOptions, null)
|
||||
: null
|
||||
const pendingWrappedOption = firstAvailableOptionIndex === null
|
||||
? null
|
||||
: flattenedOptions[firstAvailableOptionIndex]
|
||||
return {
|
||||
flattenedOptions,
|
||||
pendingWrappedOption,
|
||||
OPTION_TYPE
|
||||
},
|
||||
pendingTmNode: ref(
|
||||
props.autoPendingFirstOption
|
||||
? props.treeMate.getFirstAvailableNode()
|
||||
: null
|
||||
)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -227,38 +197,13 @@ export default {
|
||||
) return new Set(this.value)
|
||||
return null
|
||||
},
|
||||
/**
|
||||
* scrollbar related
|
||||
*/
|
||||
getScrollContainer () {
|
||||
if (this.virtualScroll) return () => this.$refs.virtualScroller && this.$refs.virtualScroller.$el
|
||||
return null
|
||||
},
|
||||
getScrollContent () {
|
||||
if (this.virtualScroll) return () => this.$refs.virtualScroller && this.$refs.virtualScroller.$refs.wrapper
|
||||
return null
|
||||
},
|
||||
pendingWrappedOptionIndex () {
|
||||
const pendingWrappedOption = this.pendingWrappedOption
|
||||
if (!pendingWrappedOption) return null
|
||||
return pendingWrappedOption.index
|
||||
},
|
||||
empty () {
|
||||
const flattenedOptions = this.flattenedOptions
|
||||
return flattenedOptions && flattenedOptions.length === 0
|
||||
const { tmNodes } = this
|
||||
return tmNodes && tmNodes.length === 0
|
||||
},
|
||||
itemSize () {
|
||||
return depx(this.cssrProps.$local[createKey('optionHeight', this.size)])
|
||||
},
|
||||
pendingOptionValue () {
|
||||
const pendingWrappedOption = this.pendingWrappedOption
|
||||
const data = (pendingWrappedOption && pendingWrappedOption.data) || null
|
||||
return (
|
||||
data &&
|
||||
data.value !== undefined &&
|
||||
data.value
|
||||
) || null
|
||||
},
|
||||
style () {
|
||||
return {
|
||||
width: formatLength(this.width)
|
||||
@ -266,54 +211,46 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options (value) {
|
||||
this.flattenedOptions = flattenOptions(value)
|
||||
treeMate (value) {
|
||||
if (this.autoPendingFirstOption) {
|
||||
const firstAvailableOptionIndex = getNextAvailableIndex(this.flattenedOptions, null)
|
||||
this.setPendingWrappedOptionIndex(firstAvailableOptionIndex)
|
||||
const tmNode = this.treeMate.getFirstAvailableNode()
|
||||
this.setPendingTmNode(tmNode)
|
||||
} else {
|
||||
this.pendingWrappedOption = null
|
||||
this.setPendingTmNode(null)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.scrollbarRef.sync()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doToggleOption (option) {
|
||||
const {
|
||||
onMenuToggleOption
|
||||
} = this
|
||||
if (onMenuToggleOption) onMenuToggleOption(option)
|
||||
},
|
||||
doScroll (e) {
|
||||
const {
|
||||
onMenuScroll
|
||||
} = this
|
||||
if (onMenuScroll) onMenuScroll(e)
|
||||
},
|
||||
// required, scroller sync need to be triggered manually
|
||||
handleListScroll () {
|
||||
this.scrollbarRef.sync()
|
||||
},
|
||||
handleListResize () {
|
||||
this.scrollbarRef.sync()
|
||||
},
|
||||
handleMenuScroll (e, scrollContainer, scrollContent) {
|
||||
const {
|
||||
onMenuScroll
|
||||
} = this
|
||||
if (onMenuScroll) onMenuScroll(e, scrollContainer, scrollContent)
|
||||
getPendingOption () {
|
||||
const { pendingTmNode } = this
|
||||
return pendingTmNode && pendingTmNode.rawNode
|
||||
},
|
||||
getPendingOptionData () {
|
||||
const pendingWrappedOption = this.pendingWrappedOption
|
||||
return pendingWrappedOption && pendingWrappedOption.data
|
||||
handleOptionMouseEnter (e, tmNode) {
|
||||
if (tmNode.disabled) return
|
||||
this.setPendingTmNode(tmNode, false)
|
||||
},
|
||||
handleOptionMouseEnter (e, index, wrappedOption) {
|
||||
const data = wrappedOption.data
|
||||
if (data.disabled) return
|
||||
this.setPendingWrappedOptionIndex(index, false)
|
||||
},
|
||||
handleOptionClick (e, index, wrappedOption) {
|
||||
const data = wrappedOption.data
|
||||
if (data.disabled || wrappedOption.as === 'dropdown-submenu') return
|
||||
this.toggleOption(data)
|
||||
},
|
||||
toggleOption (option) {
|
||||
const {
|
||||
onMenuToggleOption
|
||||
} = this
|
||||
if (onMenuToggleOption) onMenuToggleOption(option)
|
||||
},
|
||||
handleMenuMouseLeave () {
|
||||
this.pendingWrappedOption = null
|
||||
handleOptionClick (e, tmNode) {
|
||||
if (tmNode.disabled) return
|
||||
this.doToggleOption(tmNode.rawNode)
|
||||
},
|
||||
/**
|
||||
* keyboard related methods
|
||||
@ -325,36 +262,23 @@ export default {
|
||||
this.next()
|
||||
},
|
||||
next () {
|
||||
if (
|
||||
this.pendingWrappedOption === null
|
||||
) {
|
||||
this.setPendingWrappedOptionIndex(
|
||||
getNextAvailableIndex(this.flattenedOptions, null),
|
||||
true
|
||||
)
|
||||
} else {
|
||||
this.setPendingWrappedOptionIndex(
|
||||
getNextAvailableIndex(this.flattenedOptions, this.pendingWrappedOptionIndex),
|
||||
true
|
||||
)
|
||||
const {
|
||||
pendingTmNode
|
||||
} = this
|
||||
if (pendingTmNode) {
|
||||
this.setPendingTmNode(pendingTmNode.getNext(), true)
|
||||
}
|
||||
},
|
||||
prev () {
|
||||
if (this.pendingWrappedOption) {
|
||||
this.setPendingWrappedOptionIndex(
|
||||
getPrevAvailableIndex(this.flattenedOptions, this.pendingWrappedOptionIndex),
|
||||
true
|
||||
)
|
||||
const {
|
||||
pendingTmNode
|
||||
} = this
|
||||
if (pendingTmNode) {
|
||||
this.setPendingTmNode(pendingTmNode.getPrev(), true)
|
||||
}
|
||||
},
|
||||
setPendingWrappedOptionIndex (index, doScroll = false) {
|
||||
if (index === null) {
|
||||
this.pendingWrappedOption = null
|
||||
}
|
||||
// TODO: fix scroll logic
|
||||
// const scrollbar = this.scrollbarRef
|
||||
this.pendingWrappedOption = this.flattenedOptions[index]
|
||||
// scrollbar.scrollTo({ y: index * this.itemSize })
|
||||
setPendingTmNode (tmNode, doScroll = false) {
|
||||
if (tmNode !== null) this.pendingTmNode = tmNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { h, inject, toRef } from 'vue'
|
||||
import { useMemo } from '../../../_utils/composition'
|
||||
import { createClassObject } from '../../../data-table/src/utils'
|
||||
|
||||
export default {
|
||||
name: 'NBaseSelectOption',
|
||||
@ -10,30 +9,25 @@ export default {
|
||||
}
|
||||
},
|
||||
props: {
|
||||
wrappedOption: {
|
||||
tmNode: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
grouped: {
|
||||
validator (value) {
|
||||
return typeof value === 'boolean'
|
||||
},
|
||||
default: false
|
||||
},
|
||||
index: {
|
||||
validator (value) {
|
||||
return typeof value === 'number'
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
const NBaseSelectMenu = inject('NBaseSelectMenu')
|
||||
const dataRef = toRef(props.wrappedOption, 'data')
|
||||
const rawNodeRef = toRef(props.tmNode, 'rawNode')
|
||||
return {
|
||||
data: dataRef,
|
||||
rawNode: rawNodeRef,
|
||||
isGrouped: useMemo(() => {
|
||||
const { tmNode } = props
|
||||
const { parent } = tmNode
|
||||
return parent && parent.rawNode.type === 'group'
|
||||
}),
|
||||
isPending: useMemo(() => {
|
||||
return props.index === NBaseSelectMenu.pendingWrappedOptionIndex
|
||||
const { pendingTmNode } = NBaseSelectMenu
|
||||
if (!pendingTmNode) return false
|
||||
return props.tmNode.key === pendingTmNode.key
|
||||
}),
|
||||
isSelected: useMemo(() => {
|
||||
const {
|
||||
@ -41,7 +35,7 @@ export default {
|
||||
value
|
||||
} = NBaseSelectMenu
|
||||
if (value === null) return false
|
||||
const optionValue = dataRef.value.value
|
||||
const optionValue = rawNodeRef.value.value
|
||||
if (multiple) {
|
||||
const {
|
||||
valueSet
|
||||
@ -55,35 +49,45 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
handleClick (e) {
|
||||
if (this.disabled) return
|
||||
this.NBaseSelectMenu.handleOptionClick(e, this.index, this.wrappedOption)
|
||||
const {
|
||||
tmNode
|
||||
} = this
|
||||
if (tmNode.disabled) return
|
||||
this.NBaseSelectMenu.handleOptionClick(e, tmNode)
|
||||
},
|
||||
handleMouseEnter (e) {
|
||||
if (this.disabled) return
|
||||
this.NBaseSelectMenu.handleOptionMouseEnter(e, this.index, this.wrappedOption)
|
||||
const {
|
||||
tmNode
|
||||
} = this
|
||||
if (tmNode.disabled) return
|
||||
this.NBaseSelectMenu.handleOptionMouseEnter(e, tmNode)
|
||||
},
|
||||
handleMouseMove (e) {
|
||||
if (this.disabled || this.isPending) return
|
||||
this.NBaseSelectMenu.handleOptionMouseEnter(e, this.index, this.wrappedOption)
|
||||
const {
|
||||
tmNode,
|
||||
isPending
|
||||
} = this
|
||||
if (tmNode.disabled || isPending) return
|
||||
this.NBaseSelectMenu.handleOptionMouseEnter(e, tmNode)
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { data } = this
|
||||
const children = data.render ? data.render(data, this.isSelected) : [ data.label ]
|
||||
const classObject = createClassObject(data.class)
|
||||
const { rawNode } = this
|
||||
const children = rawNode.render ? rawNode.render(rawNode, this.isSelected) : [ rawNode.label ]
|
||||
return h('div', {
|
||||
class: [
|
||||
'n-base-select-option',
|
||||
{
|
||||
'n-base-select-option--selected': this.isSelected,
|
||||
'n-base-select-option--disabled': data.disabled,
|
||||
'n-base-select-option--grouped': this.grouped,
|
||||
'n-base-select-option--pending': this.isPending,
|
||||
...classObject
|
||||
}
|
||||
[
|
||||
rawNode.class,
|
||||
{
|
||||
'n-base-select-option--disabled': rawNode.disabled,
|
||||
'n-base-select-option--selected': this.isSelected,
|
||||
'n-base-select-option--grouped': this.isGrouped,
|
||||
'n-base-select-option--pending': this.isPending
|
||||
}
|
||||
]
|
||||
],
|
||||
'n-option-index': this.index,
|
||||
style: data.style,
|
||||
style: rawNode.style,
|
||||
onClick: this.handleClick,
|
||||
onMouseEnter: this.handleMouseEnter,
|
||||
onMouseMove: this.handleMouseMove
|
||||
|
@ -1,176 +0,0 @@
|
||||
|
||||
/**
|
||||
* For Select Component to use
|
||||
*/
|
||||
const OPTION_TYPE = {
|
||||
OPTION: 0,
|
||||
RENDER: 1,
|
||||
GROUP_HEADER: 2
|
||||
}
|
||||
|
||||
function valueToOptionMap (rawOptions) {
|
||||
const map = new Map()
|
||||
function traverse (options) {
|
||||
if (!Array.isArray(options)) return
|
||||
options.forEach(option => {
|
||||
if (typeof option === 'function') {
|
||||
// do nothing
|
||||
} else if (option.type === 'group') {
|
||||
traverse(option.children)
|
||||
} else {
|
||||
map.set(option.value, option)
|
||||
}
|
||||
})
|
||||
}
|
||||
traverse(rawOptions)
|
||||
return map
|
||||
}
|
||||
|
||||
function filterOptions (optionsToBeFiltered, filter) {
|
||||
if (!filter) return optionsToBeFiltered
|
||||
function traverse (options) {
|
||||
if (!Array.isArray(options)) return []
|
||||
const filteredOptions = []
|
||||
for (let option of options) {
|
||||
if (typeof option === 'function') {
|
||||
filteredOptions.push(option)
|
||||
} else if (option.type === 'group') {
|
||||
const children = traverse(option.children)
|
||||
if (children.length) {
|
||||
filteredOptions.push(Object.assign({}, option, {
|
||||
children
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
if (filter(option)) {
|
||||
filteredOptions.push(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredOptions
|
||||
}
|
||||
return traverse(optionsToBeFiltered)
|
||||
}
|
||||
|
||||
function flattenOptions (optionsToBeFlattened) {
|
||||
const flattenedOptions = []
|
||||
let index = 0
|
||||
function traverse (options, context = {}) {
|
||||
if (!Array.isArray(options)) return
|
||||
for (let option of options) {
|
||||
if (typeof option === 'function') {
|
||||
const wrappedOption = {
|
||||
type: OPTION_TYPE.RENDER,
|
||||
index: index,
|
||||
key: '__NAIVE_SELECT_RENDER__' + index,
|
||||
render: option,
|
||||
grouped: false
|
||||
}
|
||||
if (context.grouped) {
|
||||
wrappedOption.grouped = true
|
||||
}
|
||||
flattenedOptions.push(wrappedOption)
|
||||
index++
|
||||
} else if (option.type === 'render') {
|
||||
const wrappedOption = {
|
||||
type: OPTION_TYPE.RENDER,
|
||||
index: index,
|
||||
key: '__NAIVE_SELECT_RENDER__' + index,
|
||||
data: option.__NAIVE_OPTION_DATA__ || option,
|
||||
render: option.render,
|
||||
grouped: false
|
||||
}
|
||||
if (context.grouped) {
|
||||
wrappedOption.grouped = true
|
||||
}
|
||||
flattenedOptions.push(wrappedOption)
|
||||
index++
|
||||
} else if (option.type === 'group') {
|
||||
flattenedOptions.push({
|
||||
type: OPTION_TYPE.GROUP_HEADER,
|
||||
index: index,
|
||||
data: option.__NAIVE_OPTION_DATA__ || option,
|
||||
key: '__NAIVE_SELECT_GROUP_HEADER__' + index
|
||||
})
|
||||
index++
|
||||
traverse(option.children, {
|
||||
grouped: true
|
||||
})
|
||||
} else if (option.type === undefined) {
|
||||
const wrappedOption = {
|
||||
type: OPTION_TYPE.OPTION,
|
||||
as: option.as,
|
||||
index: index++,
|
||||
data: option.__NAIVE_OPTION_DATA__ || option,
|
||||
key: option.value,
|
||||
grouped: false
|
||||
}
|
||||
if (context.grouped) {
|
||||
wrappedOption.grouped = true
|
||||
}
|
||||
flattenedOptions.push(wrappedOption)
|
||||
} else {
|
||||
console.error(
|
||||
'[naive-ui/select-menu]:',
|
||||
option.type,
|
||||
'typed option is supported.'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
traverse(optionsToBeFlattened)
|
||||
return flattenedOptions
|
||||
}
|
||||
|
||||
function getNextAvailableIndex (options, currentIndex) {
|
||||
return getAvailableIndex(options, currentIndex, 'next')
|
||||
}
|
||||
|
||||
function getPrevAvailableIndex (options, currentIndex) {
|
||||
return getAvailableIndex(options, currentIndex, 'prev')
|
||||
}
|
||||
|
||||
function getAvailableIndex (options, currentIndex, direction) {
|
||||
const length = options.length
|
||||
let iterationStartsAt = null
|
||||
let iterationEndsAt = null
|
||||
if (direction === 'next') {
|
||||
if (currentIndex !== null) {
|
||||
iterationStartsAt = currentIndex + 1
|
||||
iterationEndsAt = currentIndex + length + 1
|
||||
} else {
|
||||
iterationStartsAt = 0
|
||||
iterationEndsAt = length
|
||||
}
|
||||
} else {
|
||||
if (currentIndex !== null) {
|
||||
iterationStartsAt = currentIndex + length - 1
|
||||
iterationEndsAt = currentIndex - 1
|
||||
} else {
|
||||
iterationStartsAt = length
|
||||
iterationEndsAt = 0
|
||||
}
|
||||
}
|
||||
for (let i = iterationStartsAt; i !== iterationEndsAt;) {
|
||||
const option = options[i % length]
|
||||
if (option.type === OPTION_TYPE.OPTION) {
|
||||
const data = option.data
|
||||
if (!data.disabled) return i % length
|
||||
}
|
||||
if (direction === 'prev') {
|
||||
--i
|
||||
} else {
|
||||
++i
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export {
|
||||
getPrevAvailableIndex,
|
||||
getNextAvailableIndex,
|
||||
valueToOptionMap,
|
||||
filterOptions,
|
||||
flattenOptions,
|
||||
OPTION_TYPE
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
export default function createClassObject (classValue) {
|
||||
if (typeof classValue === 'string') {
|
||||
return classValue
|
||||
.split(' ')
|
||||
.filter(v => v.length)
|
||||
.reduce((prevValue, currentValue) => {
|
||||
prevValue[currentValue] = true
|
||||
return prevValue
|
||||
}, {})
|
||||
} else if (classValue && typeof classValue === 'object') {
|
||||
return classValue
|
||||
}
|
||||
return null
|
||||
}
|
@ -57,7 +57,7 @@
|
||||
class="n-auto-complete-menu"
|
||||
:theme="mergedTheme"
|
||||
:pattern="value"
|
||||
:options="selectOptions"
|
||||
:tree-mate="treeMate"
|
||||
:multiple="false"
|
||||
:size="mergedSize"
|
||||
@menu-toggle-option="handleToggleOption"
|
||||
@ -70,6 +70,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createTreeMate } from 'treemate'
|
||||
import {
|
||||
configurable,
|
||||
themeable,
|
||||
@ -182,11 +183,19 @@ export default {
|
||||
__placeableEnabled () {
|
||||
return this.active
|
||||
},
|
||||
selectOptions () {
|
||||
return mapAutoCompleteOptionsToSelectOptions(this.options)
|
||||
},
|
||||
active () {
|
||||
return !!this.value && this.canBeActivated && !!this.selectOptions.length
|
||||
},
|
||||
selectOptions () {
|
||||
return mapAutoCompleteOptionsToSelectOptions(this.options)
|
||||
treeMate () {
|
||||
return createTreeMate(this.selectOptions, {
|
||||
getKey (node) {
|
||||
if (node.type === 'group') return node.name
|
||||
return node.value
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -250,7 +259,7 @@ export default {
|
||||
},
|
||||
handleKeyDownEnter (e) {
|
||||
if (this.$refs.menu && !this.isComposing) {
|
||||
const pendingOptionData = this.$refs.menu.getPendingOptionData()
|
||||
const pendingOptionData = this.$refs.menu.getPendingOption()
|
||||
if (pendingOptionData) {
|
||||
this.select(pendingOptionData)
|
||||
e.preventDefault()
|
||||
|
@ -21,7 +21,7 @@
|
||||
>
|
||||
<n-checkbox
|
||||
:disabled="disabled"
|
||||
:checked="checked"
|
||||
:value="checked"
|
||||
:indeterminate="indeterminate"
|
||||
@click.stop="handleCheck"
|
||||
/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
auto-pending-first-option
|
||||
:theme="theme"
|
||||
:pattern="pattern"
|
||||
:options="filteredSelectOptions"
|
||||
:tree-mate="selectTreeMate"
|
||||
:multiple="multiple"
|
||||
:size="size"
|
||||
:value="value"
|
||||
@ -30,6 +30,7 @@
|
||||
|
||||
<script>
|
||||
import { ref, inject, toRef } from 'vue'
|
||||
import { createTreeMate } from 'treemate'
|
||||
import NBaseSelectMenu from '../../_base/select-menu'
|
||||
import { createSelectOptions, getPickerElement } from './utils'
|
||||
import {
|
||||
@ -129,6 +130,15 @@ export default {
|
||||
value: option.value,
|
||||
label: option.label
|
||||
}))
|
||||
},
|
||||
selectTreeMate () {
|
||||
return createTreeMate(
|
||||
this.filteredSelectOptions, {
|
||||
getKey (node) {
|
||||
return node.value
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -195,7 +205,7 @@ export default {
|
||||
enter () {
|
||||
const { menuRef } = this
|
||||
if (menuRef) {
|
||||
const pendingOptionData = menuRef.getPendingOptionData()
|
||||
const pendingOptionData = menuRef.getPendingOption()
|
||||
this.doCheck(pendingOptionData)
|
||||
return true
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ export function useCascader (props) {
|
||||
}
|
||||
const treeMateRef = computed(() => {
|
||||
return TreeMate(props.options, {
|
||||
getKey ({ node }) {
|
||||
getKey (node) {
|
||||
return node.value
|
||||
}
|
||||
})
|
||||
|
@ -24,11 +24,11 @@
|
||||
v-for="(rowData, index) in data"
|
||||
:key="rowKey === null ? rowData.key : rowKey(rowData)"
|
||||
class="n-data-table-tr"
|
||||
:class="
|
||||
createClassObject(typeof rowClassName === 'function'
|
||||
? createClassObject(rowClassName(rowData, index))
|
||||
: rowClassName)
|
||||
"
|
||||
:class="[
|
||||
typeof rowClassName === 'function'
|
||||
? rowClassName(rowData, index)
|
||||
: rowClassName
|
||||
]"
|
||||
>
|
||||
<td
|
||||
v-for="column in columns"
|
||||
@ -39,15 +39,17 @@
|
||||
right: NDataTable.currentFixedColumnRight(column)
|
||||
}"
|
||||
class="n-data-table-td"
|
||||
:class="{
|
||||
'n-data-table-td--ellipsis': column.ellipsis,
|
||||
[`n-data-table-td--${column.align}-align`]: column.align,
|
||||
...(column.className && createClassObject(column.className)),
|
||||
[`n-data-table-td--fixed-${column.fixed}`]: column.width && column.fixed,
|
||||
'n-data-table-td--shadow-after': NBaseTable.leftActiveFixedColumn[column.key],
|
||||
'n-data-table-td--shadow-before': NBaseTable.rightActiveFixedColumn[column.key],
|
||||
'n-data-table-td--selection': column.type === 'selection'
|
||||
}"
|
||||
:class="[
|
||||
column.className,
|
||||
{
|
||||
'n-data-table-td--ellipsis': column.ellipsis,
|
||||
[`n-data-table-td--${column.align}-align`]: column.align,
|
||||
[`n-data-table-td--fixed-${column.fixed}`]: column.width && column.fixed,
|
||||
'n-data-table-td--shadow-after': NBaseTable.leftActiveFixedColumn[column.key],
|
||||
'n-data-table-td--shadow-before': NBaseTable.rightActiveFixedColumn[column.key],
|
||||
'n-data-table-td--selection': column.type === 'selection'
|
||||
}
|
||||
]"
|
||||
>
|
||||
<n-checkbox
|
||||
v-if="column.type === 'selection'"
|
||||
@ -72,7 +74,7 @@
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import Cell from './Cell.vue'
|
||||
import { createCustomWidthStyle, setCheckStatusOfRow, createClassObject, createRowKey } from '../utils'
|
||||
import { createCustomWidthStyle, setCheckStatusOfRow, createRowKey } from '../utils'
|
||||
import NScrollbar from '../../../scrollbar'
|
||||
import formatLength from '../../../_utils/css/formatLength'
|
||||
|
||||
@ -146,7 +148,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createClassObject,
|
||||
createRowKey,
|
||||
handleCheckboxInput (row, checked) {
|
||||
const newCheckedRowKeys = this.checkedRowKeys.map(v => v)
|
||||
|
@ -1,16 +1,5 @@
|
||||
import formatLength from '../../_utils/css/formatLength'
|
||||
|
||||
export function createClassObject (classString) {
|
||||
if (!classString) return {}
|
||||
if (typeof classString === 'string') {
|
||||
return classString.split(' ').filter(className => className).reduce((classObject, className) => {
|
||||
classObject[className] = true
|
||||
return classObject
|
||||
}, {})
|
||||
}
|
||||
return classString
|
||||
}
|
||||
|
||||
export function createCustomWidthStyle (column, index, placement) {
|
||||
if (column.width) {
|
||||
const width = column.width
|
||||
|
@ -14,11 +14,7 @@ import { keep, call } from '../../_utils/vue'
|
||||
import styles from './styles'
|
||||
|
||||
const treemateOptions = {
|
||||
getKey ({ parentKey, index, node }) {
|
||||
if (node.type === 'divider') {
|
||||
if (parentKey === null) return `${index}`
|
||||
return `${parentKey}-${index}`
|
||||
}
|
||||
getKey (node) {
|
||||
return node.key
|
||||
},
|
||||
getDisabled ({ node }) {
|
||||
|
@ -71,7 +71,7 @@
|
||||
auto-pending-first-option
|
||||
:theme="mergedTheme"
|
||||
:pattern="pattern"
|
||||
:options="filteredOptions"
|
||||
:tree-mate="treeMate"
|
||||
:multiple="multiple"
|
||||
:size="mergedSize"
|
||||
:filterable="filterable"
|
||||
@ -97,7 +97,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed, toRef } from 'vue'
|
||||
import { createTreeMate } from 'treemate'
|
||||
import {
|
||||
useIsMounted,
|
||||
useMergedState,
|
||||
useCompitable
|
||||
} from 'vooks'
|
||||
import {
|
||||
configurable,
|
||||
placeable,
|
||||
@ -110,13 +116,6 @@ import {
|
||||
clickoutside,
|
||||
zindexable
|
||||
} from '../../_directives'
|
||||
import {
|
||||
filterOptions,
|
||||
valueToOptionMap
|
||||
} from '../../_utils/component/select'
|
||||
import {
|
||||
useIsMounted
|
||||
} from '../../_utils/composition'
|
||||
import {
|
||||
warn
|
||||
} from '../../_utils/naive'
|
||||
@ -136,6 +135,28 @@ function patternMatched (pattern, value) {
|
||||
}
|
||||
}
|
||||
|
||||
function filterOptions (originalOpts, filter) {
|
||||
if (!filter) return originalOpts
|
||||
function traverse (options) {
|
||||
if (!Array.isArray(options)) return []
|
||||
const filteredOptions = []
|
||||
for (const option of options) {
|
||||
if (option.type === 'group') {
|
||||
const children = traverse(option.children)
|
||||
if (children.length) {
|
||||
filteredOptions.push(Object.assign({}, option, {
|
||||
children
|
||||
}))
|
||||
}
|
||||
} else if (filter(option)) {
|
||||
filteredOptions.push(option)
|
||||
}
|
||||
}
|
||||
return filteredOptions
|
||||
}
|
||||
return traverse(originalOpts)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'Select',
|
||||
components: {
|
||||
@ -167,7 +188,7 @@ export default {
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Array],
|
||||
@ -241,6 +262,10 @@ export default {
|
||||
value
|
||||
})
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
// eslint-disable-next-line vue/prop-name-casing
|
||||
'onUpdate:value': {
|
||||
type: [Function, Array],
|
||||
@ -287,23 +312,45 @@ export default {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup () {
|
||||
setup (props) {
|
||||
const patternRef = ref('')
|
||||
const filteredOptionsRef = computed(() => filterOptions(
|
||||
props.options,
|
||||
patternRef.value
|
||||
))
|
||||
const treeMateRef = computed(() => createTreeMate(filteredOptionsRef.value, {
|
||||
getKey (node) {
|
||||
if (node.type === 'group') return node.name
|
||||
return node.value
|
||||
}
|
||||
}))
|
||||
const tmNodeMap = computed(() => treeMateRef.value.treeNodeMap)
|
||||
const uncontrolledShowRef = ref(false)
|
||||
const mergedShowRef = useMergedState(
|
||||
toRef(props, 'show'),
|
||||
uncontrolledShowRef
|
||||
)
|
||||
return {
|
||||
treeMate: treeMateRef,
|
||||
flattenedNodes: computed(() => {
|
||||
return treeMateRef.value.flattenedNodes
|
||||
}),
|
||||
tmNodeMap,
|
||||
isMounted: useIsMounted(),
|
||||
offsetContainerRef: ref(null),
|
||||
triggerRef: ref(null),
|
||||
trackingRef: ref(null),
|
||||
menuRef: ref(null)
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
show: false,
|
||||
scrolling: false,
|
||||
pattern: '',
|
||||
memorizedValueToOptionMap: new Map(),
|
||||
createdOptions: [],
|
||||
beingCreatedOptions: []
|
||||
menuRef: ref(null),
|
||||
pattern: patternRef,
|
||||
uncontrolledShow: uncontrolledShowRef,
|
||||
show: mergedShowRef,
|
||||
compitableOptions: useCompitable(props, [
|
||||
'items',
|
||||
'options'
|
||||
]),
|
||||
createdOptions: ref([]),
|
||||
beingCreatedOptions: ref([]),
|
||||
memoValOptMap: ref(new Map())
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -311,67 +358,76 @@ export default {
|
||||
return this.show
|
||||
},
|
||||
localizedPlaceholder () {
|
||||
if (this.placeholder !== undefined) {
|
||||
return this.placeholder
|
||||
}
|
||||
return this.localeNs.placeholder
|
||||
},
|
||||
adpatedOptions () {
|
||||
/**
|
||||
* If using deprecated API, make it work at first
|
||||
*/
|
||||
const options = this.items || this.options
|
||||
if (options) return options
|
||||
return []
|
||||
return this.placeholder ?? this.localeNs.placeholder
|
||||
},
|
||||
localOptions () {
|
||||
return this.adpatedOptions
|
||||
return this.compitableOptions
|
||||
.concat(this.createdOptions)
|
||||
.concat(this.beingCreatedOptions)
|
||||
},
|
||||
filteredOptions () {
|
||||
if (this.remote) {
|
||||
return this.adpatedOptions
|
||||
return this.compitableOptions
|
||||
} else {
|
||||
const options = this.localOptions
|
||||
const trimmedPattern = this.pattern.trim()
|
||||
if (!trimmedPattern.length || !this.filterable) {
|
||||
return options
|
||||
const { localOptions, pattern } = this
|
||||
if (!pattern.length || !this.filterable) {
|
||||
return localOptions
|
||||
} else {
|
||||
const filter = option => this.filter(trimmedPattern, option)
|
||||
const filteredOptions = filterOptions(options, filter)
|
||||
return filteredOptions
|
||||
const { filter } = this
|
||||
const mergedFilter = option => filter(pattern, option)
|
||||
return filterOptions(localOptions, mergedFilter)
|
||||
}
|
||||
}
|
||||
},
|
||||
valueToOptionMap () {
|
||||
return valueToOptionMap(this.localOptions)
|
||||
},
|
||||
selectedOptions () {
|
||||
if (this.multiple) {
|
||||
return this.mapValuesToOptions(this.value)
|
||||
const { value: values } = this
|
||||
if (!Array.isArray(values)) return []
|
||||
const remote = this.remote
|
||||
const {
|
||||
tmNodeMap,
|
||||
memoValOptMap,
|
||||
wrappedFallbackOption
|
||||
} = this
|
||||
const options = []
|
||||
values.forEach(value => {
|
||||
if (tmNodeMap.has(value)) {
|
||||
options.push(tmNodeMap.get(value).rawNode)
|
||||
} else if (remote && memoValOptMap.has(value)) {
|
||||
options.push(memoValOptMap.get(value))
|
||||
} else if (wrappedFallbackOption) {
|
||||
const option = wrappedFallbackOption(value)
|
||||
if (option) {
|
||||
options.push(option)
|
||||
}
|
||||
}
|
||||
})
|
||||
return options
|
||||
}
|
||||
return null
|
||||
},
|
||||
selectedOption () {
|
||||
if (!this.multiple) {
|
||||
const value = this.value
|
||||
const selectedOption = this.getOption(value)
|
||||
const fallbackOption = this.wrappedFallbackOption
|
||||
const { value, tmNodeMap, wrappedFallbackOption } = this
|
||||
let selectedOption = null
|
||||
if (tmNodeMap.has(value)) {
|
||||
selectedOption = tmNodeMap.get(value).rawNode
|
||||
} else if (this.remote) {
|
||||
selectedOption = this.memoValOptMap.get(value)
|
||||
}
|
||||
return (
|
||||
selectedOption ||
|
||||
(fallbackOption && fallbackOption(value)) ||
|
||||
(wrappedFallbackOption && wrappedFallbackOption(value)) ||
|
||||
null
|
||||
)
|
||||
}
|
||||
return null
|
||||
},
|
||||
wrappedFallbackOption () {
|
||||
const fallbackOption = this.fallbackOption
|
||||
const { fallbackOption } = this
|
||||
if (!fallbackOption) return false
|
||||
return value => {
|
||||
const type = typeof value
|
||||
if (type === 'string' || type === 'number') {
|
||||
if (['string', 'number'].includes(typeof value)) {
|
||||
return Object.assign(fallbackOption(value), { value })
|
||||
} return null
|
||||
}
|
||||
@ -382,9 +438,11 @@ export default {
|
||||
this.updateMemorizedOptions()
|
||||
},
|
||||
filteredOptions () {
|
||||
if (!this.show) return
|
||||
this.$nextTick(this.__placeableSyncPosition)
|
||||
},
|
||||
value () {
|
||||
if (!this.show) return
|
||||
this.$nextTick(this.__placeableSyncPosition)
|
||||
}
|
||||
},
|
||||
@ -444,28 +502,24 @@ export default {
|
||||
} = this
|
||||
if (onScroll) call(onScroll, ...args)
|
||||
},
|
||||
activate () {
|
||||
this.show = true
|
||||
},
|
||||
deactivate () {
|
||||
this.show = false
|
||||
},
|
||||
/**
|
||||
* remote related methods
|
||||
*/
|
||||
updateMemorizedOptions () {
|
||||
const remote = this.remote
|
||||
const multiple = this.multiple
|
||||
const {
|
||||
remote,
|
||||
multiple
|
||||
} = this
|
||||
if (remote) {
|
||||
const memorizedValueToOptionMap = this.memorizedValueToOptionMap
|
||||
const { memoValOptMap } = this
|
||||
if (multiple) {
|
||||
this.selectedOptions.forEach(option => {
|
||||
memorizedValueToOptionMap.set(option.value, option)
|
||||
memoValOptMap.set(option.value, option)
|
||||
})
|
||||
} else {
|
||||
const option = this.selectedOption
|
||||
if (option) {
|
||||
memorizedValueToOptionMap.set(option.value, option)
|
||||
memoValOptMap.set(option.value, option)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -476,14 +530,14 @@ export default {
|
||||
openMenu () {
|
||||
if (!this.disabled) {
|
||||
this.pattern = ''
|
||||
this.activate()
|
||||
this.uncontrolledShow = true
|
||||
if (this.filterable) {
|
||||
this.triggerRef.focusPatternInput()
|
||||
}
|
||||
}
|
||||
},
|
||||
closeMenu () {
|
||||
this.deactivate()
|
||||
this.uncontrolledShow = false
|
||||
},
|
||||
handleMenuAfterLeave () {
|
||||
this.pattern = ''
|
||||
@ -500,9 +554,6 @@ export default {
|
||||
},
|
||||
handleTriggerBlur () {
|
||||
this.doBlur()
|
||||
if (__DEV__) {
|
||||
if (this.debug) return
|
||||
}
|
||||
this.closeMenu()
|
||||
},
|
||||
handleTriggerFocus () {
|
||||
@ -510,65 +561,35 @@ export default {
|
||||
},
|
||||
handleMenuClickOutside (e) {
|
||||
if (this.show) {
|
||||
if (!this.triggerRef.$el.contains(e.target) && !this.scrolling) {
|
||||
if (!this.triggerRef.$el.contains(e.target)) {
|
||||
this.closeMenu()
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* data utils methods
|
||||
*/
|
||||
mapValuesToOptions (values) {
|
||||
if (!Array.isArray(values)) return []
|
||||
const remote = this.remote
|
||||
const valueOptionMap = this.valueToOptionMap
|
||||
const memorizedValueToOptionMap = this.memorizedValueToOptionMap
|
||||
const options = []
|
||||
const fallbackOption = this.wrappedFallbackOption
|
||||
values.forEach(value => {
|
||||
if (valueOptionMap.has(value)) {
|
||||
options.push(valueOptionMap.get(value))
|
||||
} else if (remote && memorizedValueToOptionMap.has(value)) {
|
||||
options.push(memorizedValueToOptionMap.get(value))
|
||||
} else if (fallbackOption) {
|
||||
const option = fallbackOption(value)
|
||||
if (option) {
|
||||
options.push(option)
|
||||
}
|
||||
}
|
||||
})
|
||||
return options
|
||||
},
|
||||
getOption (value) {
|
||||
if (this.valueToOptionMap.has(value)) {
|
||||
return this.valueToOptionMap.get(value)
|
||||
} else if (this.remote && this.memorizedValueToOptionMap.has(value)) {
|
||||
return this.memorizedValueToOptionMap.get(value)
|
||||
}
|
||||
},
|
||||
createClearedMultipleSelectValue (value) {
|
||||
if (!Array.isArray(value)) return []
|
||||
if (this.wrappedFallbackOption) {
|
||||
/** if option has a fallback, I can't help user to clear some unknown value */
|
||||
// if option has a fallback, I can't help user to clear some unknown value
|
||||
return Array.from(value)
|
||||
} else {
|
||||
/** if there's no option fallback, unappeared options are treated as invalid */
|
||||
const remote = this.remote
|
||||
const valueOptionMap = this.valueToOptionMap
|
||||
// if there's no option fallback, unappeared options are treated as invalid
|
||||
const {
|
||||
remote,
|
||||
tmNodeMap
|
||||
} = this
|
||||
if (remote) {
|
||||
const memorizedValueToOptionMap = this.memorizedValueToOptionMap
|
||||
return value.filter(v => valueOptionMap.has(v) || memorizedValueToOptionMap.has(v))
|
||||
const { memoValOptMap } = this
|
||||
return value.filter(v => tmNodeMap.has(v) || memoValOptMap.has(v))
|
||||
} else {
|
||||
return value.filter(v => valueOptionMap.has(v))
|
||||
return value.filter(v => tmNodeMap.has(v))
|
||||
}
|
||||
}
|
||||
},
|
||||
handleToggleOption (option) {
|
||||
if (this.disabled) return
|
||||
const tag = this.tag
|
||||
const remote = this.remote
|
||||
const { tag, remote } = this
|
||||
if (tag && !remote) {
|
||||
const beingCreatedOptions = this.beingCreatedOptions
|
||||
const { beingCreatedOptions } = this
|
||||
const beingCreatedOption = beingCreatedOptions[0] || null
|
||||
if (beingCreatedOption) {
|
||||
this.createdOptions.push(beingCreatedOption)
|
||||
@ -577,7 +598,7 @@ export default {
|
||||
}
|
||||
if (this.multiple) {
|
||||
if (remote) {
|
||||
this.memorizedValueToOptionMap.set(option.value, option)
|
||||
this.memoValOptMap.set(option.value, option)
|
||||
}
|
||||
const changedValue = this.createClearedMultipleSelectValue(this.value)
|
||||
const index = changedValue.findIndex(value => value === option.value)
|
||||
@ -615,8 +636,8 @@ export default {
|
||||
if (!this.pattern.length) {
|
||||
const changedValue = this.createClearedMultipleSelectValue(this.value)
|
||||
if (Array.isArray(changedValue)) {
|
||||
const popedValue = changedValue.pop()
|
||||
const createdOptionIndex = this.getCreatedOptionIndex(popedValue)
|
||||
const poppedValue = changedValue.pop()
|
||||
const createdOptionIndex = this.getCreatedOptionIndex(poppedValue)
|
||||
~createdOptionIndex && this.createdOptions.splice(createdOptionIndex, 1)
|
||||
this.doUpdateValue(changedValue)
|
||||
}
|
||||
@ -631,19 +652,19 @@ export default {
|
||||
handlePatternInput (e) {
|
||||
const value = e.target.value
|
||||
this.pattern = value
|
||||
const onSearch = this.onSearch
|
||||
const { onSearch, tag, remote } = this
|
||||
if (onSearch) {
|
||||
onSearch(value)
|
||||
this.doSearch(value)
|
||||
}
|
||||
if (this.tag && !this.remote) {
|
||||
if (tag && !remote) {
|
||||
if (!value) {
|
||||
this.beingCreatedOptions = []
|
||||
return
|
||||
}
|
||||
const optionBeingCreated = this.onCreate(value)
|
||||
if (
|
||||
this.adpatedOptions.some(
|
||||
this.compitableOptions.some(
|
||||
option => option.value === optionBeingCreated.value
|
||||
) ||
|
||||
this.createdOptions.some(
|
||||
@ -658,28 +679,25 @@ export default {
|
||||
},
|
||||
handleClear (e) {
|
||||
e.stopPropagation()
|
||||
if (!this.multiple && this.filterable) {
|
||||
const { multiple, doUpdateValue } = this
|
||||
if (!multiple && this.filterable) {
|
||||
this.closeMenu()
|
||||
}
|
||||
if (this.multiple) {
|
||||
this.doUpdateValue([])
|
||||
if (multiple) {
|
||||
doUpdateValue([])
|
||||
} else {
|
||||
this.doUpdateValue(null)
|
||||
doUpdateValue(null)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* scroll events on menu
|
||||
*/
|
||||
// scroll events on menu
|
||||
handleMenuScroll (e, scrollContainer, scrollContent) {
|
||||
this.doScroll(e, scrollContainer, scrollContent)
|
||||
},
|
||||
/**
|
||||
* keyboard events
|
||||
*/
|
||||
// keyboard events
|
||||
handleKeyUpEnter (e) {
|
||||
if (this.show) {
|
||||
const menu = this.menuRef
|
||||
const pendingOptionData = menu && menu.getPendingOptionData()
|
||||
const pendingOptionData = menu && menu.getPendingOption()
|
||||
if (pendingOptionData) {
|
||||
this.handleToggleOption(pendingOptionData)
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user