mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2024-12-15 04:42:23 +08:00
feat(select): remote search on multiple select
This commit is contained in:
parent
be3221d7bf
commit
b44b17e8be
@ -17,6 +17,7 @@
|
|||||||
<change-event />
|
<change-event />
|
||||||
<change-event-emit-item />
|
<change-event-emit-item />
|
||||||
<search />
|
<search />
|
||||||
|
<remote-search />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -28,6 +29,7 @@ import multipleSelect from './multipleSelect.demo.vue'
|
|||||||
import changeEvent from './changeEvent.demo'
|
import changeEvent from './changeEvent.demo'
|
||||||
import changeEventEmitItem from './changeEventEmitItem.demo'
|
import changeEventEmitItem from './changeEventEmitItem.demo'
|
||||||
import search from './search.demo.vue'
|
import search from './search.demo.vue'
|
||||||
|
import remoteSearch from './remoteSearch.demo.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -36,7 +38,8 @@ export default {
|
|||||||
changeEvent,
|
changeEvent,
|
||||||
changeEventEmitItem,
|
changeEventEmitItem,
|
||||||
search,
|
search,
|
||||||
disabled
|
disabled,
|
||||||
|
remoteSearch
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
107
demo/components/selectDemo/remoteSearch.demo.vue
Normal file
107
demo/components/selectDemo/remoteSearch.demo.vue
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<div class="n-doc-section">
|
||||||
|
<div class="n-doc-section__header">
|
||||||
|
Remote Search
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="n-doc-section__view"
|
||||||
|
style="flex-wrap: nowrap;"
|
||||||
|
>
|
||||||
|
<!--EXAMPLE_START-->
|
||||||
|
<n-select
|
||||||
|
v-model="selectedValues"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
placeholder="Search Songs"
|
||||||
|
:items="items"
|
||||||
|
:on-search="handleSearch"
|
||||||
|
remote
|
||||||
|
:no-data-content="noDataContent"
|
||||||
|
:loading="loading"
|
||||||
|
style="flex-grow: 1; margin-right: 12px; width: 300px;"
|
||||||
|
/>
|
||||||
|
<!--EXAMPLE_END-->
|
||||||
|
</div>
|
||||||
|
<pre class="n-doc-section__inspect">v-model(multiple): {{ JSON.stringify(selectedValues) }}</pre>
|
||||||
|
<n-doc-source-block>
|
||||||
|
<!--SOURCE-->
|
||||||
|
</n-doc-source-block>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
label: 'Drive My Car',
|
||||||
|
value: 'song1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Norwegian Wood',
|
||||||
|
value: 'song2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'You Won\'t See',
|
||||||
|
value: 'song3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Nowhere Man',
|
||||||
|
value: 'song4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Think For Yourself',
|
||||||
|
value: 'song5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'The Word',
|
||||||
|
value: 'song6'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Michelle',
|
||||||
|
value: 'song7'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'What goes on',
|
||||||
|
value: 'song8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Girl',
|
||||||
|
value: 'song9'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'I\'m looking through you',
|
||||||
|
value: 'song10'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'In My Life',
|
||||||
|
value: 'song11'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Wait',
|
||||||
|
value: 'song12'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
selectedValues: null,
|
||||||
|
loading: false,
|
||||||
|
items: [],
|
||||||
|
noDataContent: 'please search',
|
||||||
|
handleSearch: (query) => {
|
||||||
|
if (!query.length) {
|
||||||
|
this.items = []
|
||||||
|
this.noDataContent = 'please search'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.items = items.filter(item => ~item.label.search(query))
|
||||||
|
if (!this.items.length) this.noDataContent = 'no result found'
|
||||||
|
this.loading = false
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -4,9 +4,9 @@
|
|||||||
class="n-select"
|
class="n-select"
|
||||||
:class="{
|
:class="{
|
||||||
[`n-select--${size}-size`]: true,
|
[`n-select--${size}-size`]: true,
|
||||||
|
[`n-select--remote`]: remote,
|
||||||
'n-select--disabled': disabled
|
'n-select--disabled': disabled
|
||||||
}"
|
}"
|
||||||
:style="{'cursor':cursor}"
|
|
||||||
@click="handleActivatorClick"
|
@click="handleActivatorClick"
|
||||||
@keyup.up.prevent="handleActivatorKeyUpUp"
|
@keyup.up.prevent="handleActivatorKeyUpUp"
|
||||||
@keyup.down.prevent="handleActivatorKeyUpDown"
|
@keyup.down.prevent="handleActivatorKeyUpDown"
|
||||||
@ -63,9 +63,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="n-select-link__placeholder"
|
class="n-select-link__placeholder"
|
||||||
:class="{
|
|
||||||
'n-select-link__placeholder--verbose-transition': verboseTransition
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
{{ placeholder }}
|
{{ placeholder }}
|
||||||
</div>
|
</div>
|
||||||
@ -103,24 +100,26 @@
|
|||||||
:style="{ top: `${lightBarTop}px` }"
|
:style="{ top: `${lightBarTop}px` }"
|
||||||
/>
|
/>
|
||||||
</transition>
|
</transition>
|
||||||
|
<template v-if="!loading">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in filteredItems"
|
||||||
|
ref="menuItems"
|
||||||
|
:key="item.value"
|
||||||
|
:data-index="index"
|
||||||
|
class="n-select-menu__item"
|
||||||
|
:class="{
|
||||||
|
'n-select-menu__item--selected':
|
||||||
|
isSelected(item)
|
||||||
|
}"
|
||||||
|
@click="toggleItem(item)"
|
||||||
|
@mousemove="showLightBarTop($event, item, index)"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in filteredItems"
|
v-if="loading"
|
||||||
ref="menuItems"
|
class="n-select-menu__item n-select-menu__item--loading"
|
||||||
:key="item.value"
|
|
||||||
:data-index="index"
|
|
||||||
class="n-select-menu__item"
|
|
||||||
:class="{
|
|
||||||
'n-select-menu__item--selected':
|
|
||||||
isSelected(item)
|
|
||||||
}"
|
|
||||||
@click="toggleItem(item)"
|
|
||||||
@mousemove="showLightBarTop($event, item, index)"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="pattern.length && !filteredItems.length"
|
|
||||||
class="n-select-menu__item n-select-menu__item--not-found"
|
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
/**
|
/**
|
||||||
@ -129,7 +128,25 @@
|
|||||||
*/
|
*/
|
||||||
hideLightBar()
|
hideLightBar()
|
||||||
}}
|
}}
|
||||||
none result matched
|
loading
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="items && items.length === 0"
|
||||||
|
class="n-select-menu__item n-select-menu__item--no-data"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
hideLightBar()
|
||||||
|
}}
|
||||||
|
{{ noDataContent }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="pattern.length && !filteredItems.length"
|
||||||
|
class="n-select-menu__item n-select-menu__item--not-found"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
hideLightBar()
|
||||||
|
}}
|
||||||
|
{{ notFoundContent }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</scrollbar>
|
</scrollbar>
|
||||||
@ -203,9 +220,25 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
cursor: {
|
remote: {
|
||||||
type: String,
|
type: Boolean,
|
||||||
default: 'inherit'
|
default: false
|
||||||
|
},
|
||||||
|
onSearch: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
noDataContent: {
|
||||||
|
type: [String, Function],
|
||||||
|
default: 'no data'
|
||||||
|
},
|
||||||
|
notFoundContent: {
|
||||||
|
type: [String, Function],
|
||||||
|
default: 'none result matched'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
@ -215,21 +248,19 @@ export default {
|
|||||||
scrolling: false,
|
scrolling: false,
|
||||||
pattern: '',
|
pattern: '',
|
||||||
pendingItem: null,
|
pendingItem: null,
|
||||||
pendingItemIndex: null
|
pendingItemIndex: null,
|
||||||
|
memorizedValueItemMap: new Map()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredItems () {
|
filteredItems () {
|
||||||
if (!this.filterable || !this.pattern.trim().length) return this.items
|
if (this.remote) {
|
||||||
|
return this.items
|
||||||
|
} else if (!this.filterable || !this.pattern.trim().length) return this.items
|
||||||
return this.items.filter(item => this.patternMatched(item.label))
|
return this.items.filter(item => this.patternMatched(item.label))
|
||||||
},
|
},
|
||||||
selected () {
|
selected () {
|
||||||
if (Array.isArray(this.value)) {
|
return this.selectedItems.length
|
||||||
const itemValues = new Set(this.items.map(item => item.value))
|
|
||||||
return this.value.filter(value => itemValues.has(value)).length
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
valueItemMap () {
|
valueItemMap () {
|
||||||
const valueToItem = new Map()
|
const valueToItem = new Map()
|
||||||
@ -238,7 +269,13 @@ export default {
|
|||||||
},
|
},
|
||||||
selectedItems () {
|
selectedItems () {
|
||||||
if (!Array.isArray(this.value)) return []
|
if (!Array.isArray(this.value)) return []
|
||||||
return this.value.filter(value => this.valueItemMap.has(value)).map(value => this.valueItemMap.get(value))
|
if (this.remote) {
|
||||||
|
return this.value
|
||||||
|
.filter(value => this.valueItemMap.has(value) || this.memorizedValueItemMap.has(value))
|
||||||
|
.map(value => this.valueItemMap.has(value) ? this.valueItemMap.get(value) : this.memorizedValueItemMap.get(value))
|
||||||
|
} else {
|
||||||
|
return this.value.filter(value => this.valueItemMap.has(value)).map(value => this.valueItemMap.get(value))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clearedPattern () {
|
clearedPattern () {
|
||||||
return this.pattern.toLowerCase().trim()
|
return this.pattern.toLowerCase().trim()
|
||||||
@ -332,10 +369,13 @@ export default {
|
|||||||
},
|
},
|
||||||
toggleItem (item) {
|
toggleItem (item) {
|
||||||
if (this.disabled) return
|
if (this.disabled) return
|
||||||
|
if (this.remote) {
|
||||||
|
this.memorizedValueItemMap.set(item.value, item)
|
||||||
|
}
|
||||||
let newValue = []
|
let newValue = []
|
||||||
if (Array.isArray(this.value)) {
|
if (Array.isArray(this.value)) {
|
||||||
const itemValues = new Set(this.items.map(item => item.value))
|
const itemValues = new Set(this.items.map(item => item.value))
|
||||||
newValue = this.value.filter(value => itemValues.has(value))
|
newValue = this.value.filter(value => itemValues.has(value) || this.memorizedValueItemMap.has(value))
|
||||||
}
|
}
|
||||||
const index = newValue.findIndex(value => value === item.value)
|
const index = newValue.findIndex(value => value === item.value)
|
||||||
if (~index) {
|
if (~index) {
|
||||||
@ -365,6 +405,9 @@ export default {
|
|||||||
this.$nextTick().then(() => {
|
this.$nextTick().then(() => {
|
||||||
const textWidth = this.$refs.inputTagMirror.getBoundingClientRect().width
|
const textWidth = this.$refs.inputTagMirror.getBoundingClientRect().width
|
||||||
this.$refs.inputTagInput.style.width = textWidth + 'px'
|
this.$refs.inputTagInput.style.width = textWidth + 'px'
|
||||||
|
if (this.onSearch) {
|
||||||
|
this.onSearch(this.pattern)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handlePatternInputDelete (e) {
|
handlePatternInputDelete (e) {
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
[`n-select--${size}-size`]: true,
|
[`n-select--${size}-size`]: true,
|
||||||
'n-select--disabled': disabled
|
'n-select--disabled': disabled
|
||||||
}"
|
}"
|
||||||
:style="{'cursor':cursor}"
|
|
||||||
@click="toggleMenu"
|
@click="toggleMenu"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -154,11 +153,18 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
cursor: {
|
remote: {
|
||||||
type: String,
|
type: Boolean,
|
||||||
default: 'inherit'
|
default: false
|
||||||
|
},
|
||||||
|
onSearch: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
@ -16,8 +16,11 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line vue/require-prop-types
|
|
||||||
value: {
|
value: {
|
||||||
|
validator () {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
required: false,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
placeholder: {
|
placeholder: {
|
||||||
@ -48,9 +51,21 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
cursor: {
|
remote: {
|
||||||
type: String,
|
type: Boolean,
|
||||||
default: 'inherit'
|
default: false
|
||||||
|
},
|
||||||
|
onSearch: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
noDataContent: {
|
||||||
|
type: [String, Function],
|
||||||
|
default: 'no data'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
|
@ -10,6 +10,13 @@
|
|||||||
font-family: $default-font-family;
|
font-family: $default-font-family;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
&.n-select--remote {
|
||||||
|
.n-select-link {
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
&.n-select--disabled {
|
&.n-select--disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
.n-select-link {
|
.n-select-link {
|
||||||
@ -295,10 +302,18 @@
|
|||||||
&.n-select-menu__item--selected {
|
&.n-select-menu__item--selected {
|
||||||
color: #63E2B7FF;
|
color: #63E2B7FF;
|
||||||
}
|
}
|
||||||
|
&.n-select-menu__item--no-data {
|
||||||
|
color: rgba(255, 255, 255, .5);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
&.n-select-menu__item--not-found {
|
&.n-select-menu__item--not-found {
|
||||||
color: rgba(255, 255, 255, .5);
|
color: rgba(255, 255, 255, .5);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
&.n-select-menu__item--loading {
|
||||||
|
color: rgba(255, 255, 255, .5);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.n-select-menu__light-bar {
|
.n-select-menu__light-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
Loading…
Reference in New Issue
Block a user