mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-01-18 12:34:25 +08:00
refactor(select): support main keyboard events
This commit is contained in:
parent
6bba332083
commit
74eb78fd16
@ -55,6 +55,7 @@
|
||||
<template v-if="multiple && filterable">
|
||||
<!-- multiple filterable -->
|
||||
<div
|
||||
ref="patternInputWrapper"
|
||||
class="n-base-picker-tags"
|
||||
:class="{
|
||||
'n-base-picker-tags--selected': selected
|
||||
@ -113,6 +114,7 @@
|
||||
<template v-else-if="!multiple && filterable">
|
||||
<!-- single filterable -->
|
||||
<div
|
||||
ref="patternInputWrapper"
|
||||
class="n-base-picker-label"
|
||||
:tabindex="(!disabled && !active) ? '0' : false"
|
||||
>
|
||||
@ -208,10 +210,6 @@ export default {
|
||||
},
|
||||
default: null
|
||||
},
|
||||
toggleOption: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@ -283,8 +281,7 @@ export default {
|
||||
}
|
||||
},
|
||||
handleDeleteOption (option) {
|
||||
this.$emit('delete-option')
|
||||
this.toggleOption(option)
|
||||
this.$emit('delete-option', option)
|
||||
},
|
||||
handlePatternKeyDownDelete (option) {
|
||||
if (!this.pattern.length) {
|
||||
@ -312,6 +309,11 @@ export default {
|
||||
this.patternInputFocused = false
|
||||
this.handleBlur()
|
||||
},
|
||||
focusPatternInputWrapper () {
|
||||
if (this.$refs.patternInputWrapper) {
|
||||
this.$refs.patternInputWrapper.focus()
|
||||
}
|
||||
},
|
||||
focusPatternInput () {
|
||||
if (this.$refs.patternInput) {
|
||||
console.log('focusPatternInput')
|
||||
|
@ -25,7 +25,7 @@
|
||||
</transition>
|
||||
<template v-if="!loading">
|
||||
<div
|
||||
v-for="option in processedOptions"
|
||||
v-for="option in linkedOptions"
|
||||
ref="menuOptions"
|
||||
:key="option.value"
|
||||
:data-id="option.id"
|
||||
@ -65,7 +65,7 @@
|
||||
{{ noDataContent }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="filterable && (pattern.length && !processedOptions.length)"
|
||||
v-else-if="filterable && (pattern.length && !linkedOptions.length)"
|
||||
class="n-base-select-menu__item n-base-select-menu__item--not-found"
|
||||
>
|
||||
{{
|
||||
@ -93,7 +93,7 @@ export default {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
processedOptions: {
|
||||
linkedOptions: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
@ -142,15 +142,15 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
firstOption () {
|
||||
// console.log(this.processedOptions)
|
||||
if (this.processedOptions && this.processedOptions.length) {
|
||||
return this.processedOptions[0]
|
||||
// console.log(this.linkedOptions)
|
||||
if (this.linkedOptions && this.linkedOptions.length) {
|
||||
return this.linkedOptions[0]
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
processedOptions () {
|
||||
linkedOptions () {
|
||||
this.$nextTick().then(() => {
|
||||
this.hideLightBar()
|
||||
this.pendingOption = null
|
||||
|
@ -36,7 +36,7 @@
|
||||
:options="options"
|
||||
:multiple="multiple"
|
||||
:size="size"
|
||||
:processed-options="processedSelectOptions"
|
||||
:processed-options="linkedSelectOptions"
|
||||
:is-selected="isSelected"
|
||||
@menu-toggle-option="handleSelectMenuToggleOption"
|
||||
@menu-scroll-start="handleMenuScrollStart"
|
||||
@ -50,7 +50,7 @@
|
||||
import NCascaderSubmenu from './CascaderSubmenu'
|
||||
import NBaseSelectMenu from '../../../base/SelectMenu'
|
||||
import { getType, traverseWithCallback, isLeaf, minus, merge } from './utils'
|
||||
import processedOptions from '../../../utils/component/processOptions'
|
||||
import linkedOptions from '../../../utils/component/linkedOptions'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
function processedOption (option, activeIds, tracedOption) {
|
||||
@ -226,8 +226,8 @@ export default {
|
||||
})
|
||||
return filteredSelectOptions
|
||||
},
|
||||
processedSelectOptions () {
|
||||
return processedOptions(this.filteredSelectOptions, this)
|
||||
linkedSelectOptions () {
|
||||
return linkedOptions(this.filteredSelectOptions, this)
|
||||
},
|
||||
activeOptionPath () {
|
||||
return this.optionPath(this.activeId)
|
||||
|
@ -9,6 +9,7 @@
|
||||
@keyup.down="handleKeyUpDown"
|
||||
@keyup.enter="handleKeyUpEnter"
|
||||
@keyup.space="handleKeyUpSpace"
|
||||
@keyup.esc="handleKeyUpEsc"
|
||||
>
|
||||
<n-base-picker
|
||||
ref="activator"
|
||||
@ -18,7 +19,6 @@
|
||||
:placeholder="placeholder"
|
||||
:selected-option="selectedOption"
|
||||
:selected-options="selectedOptions"
|
||||
:toggle-option="toggleOption"
|
||||
:multiple="multiple"
|
||||
:filterable="filterable"
|
||||
:remote="remote"
|
||||
@ -28,6 +28,7 @@
|
||||
:size="size"
|
||||
@click="handleActivatorClick"
|
||||
@delete-last-option="handleDeleteLastOption"
|
||||
@delete-option="handleToggleOption"
|
||||
@pattern-input="handlePatternInput"
|
||||
@clear="handleClear"
|
||||
@blur="handlePickerBlur"
|
||||
@ -50,14 +51,14 @@
|
||||
:options="options"
|
||||
:multiple="multiple"
|
||||
:size="size"
|
||||
:processed-options="processedOptions"
|
||||
:linked-options="linkedOptions"
|
||||
:loading="loading"
|
||||
:no-data-content="noDataContent"
|
||||
:not-found-content="notFoundContent"
|
||||
:emit-option="emitOption"
|
||||
:filterable="filterable"
|
||||
:is-selected="isSelected"
|
||||
@menu-toggle-option="toggleOption"
|
||||
@menu-toggle-option="handleToggleOption"
|
||||
@menu-scroll="handleMenuScroll"
|
||||
@menu-scroll-start="handleMenuScrollStart"
|
||||
@menu-scroll-end="handleMenuScrollEnd"
|
||||
@ -77,7 +78,7 @@ import Emitter from '../../../mixins/emitter'
|
||||
import clickoutside from '../../../directives/clickoutside'
|
||||
import NBaseSelectMenu from '../../../base/SelectMenu'
|
||||
import NBasePicker from '../../../base/Picker'
|
||||
import processedOptions from '../../../utils/component/processOptions'
|
||||
import linkedOptions from '../../../utils/component/linkedOptions'
|
||||
|
||||
export default {
|
||||
name: 'NBaseSelect',
|
||||
@ -122,10 +123,6 @@ export default {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
verboseTransition: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
emitOption: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@ -168,8 +165,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
firstOption () {
|
||||
if (this.processedOptions && this.processedOptions.length) {
|
||||
return this.processedOptions[0]
|
||||
if (this.linkedOptions && this.linkedOptions.length) {
|
||||
return this.linkedOptions[0]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
@ -180,8 +177,8 @@ export default {
|
||||
} else if (!this.filterable || !this.pattern.trim().length) return this.options
|
||||
return this.options.filter(option => this.patternMatched(option.label))
|
||||
},
|
||||
processedOptions () {
|
||||
return processedOptions(this.filteredOptions)
|
||||
linkedOptions () {
|
||||
return linkedOptions(this.filteredOptions)
|
||||
},
|
||||
valueOptionMap () {
|
||||
const valueToOption = new Map()
|
||||
@ -242,7 +239,40 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @param {string} value
|
||||
* menu related methods
|
||||
*/
|
||||
openMenu () {
|
||||
this.pattern = ''
|
||||
this.activate()
|
||||
if (this.filterable) {
|
||||
this.$refs.activator.focusPatternInput()
|
||||
}
|
||||
},
|
||||
closeMenu () {
|
||||
this.deactivate()
|
||||
},
|
||||
handleActivatorClick () {
|
||||
if (this.disabled) return
|
||||
if (!this.active) {
|
||||
this.openMenu()
|
||||
} else {
|
||||
if (!this.filterable) {
|
||||
this.closeMenu()
|
||||
}
|
||||
}
|
||||
},
|
||||
handlePickerBlur () {
|
||||
this.closeMenu()
|
||||
},
|
||||
handleClickOutsideMenu (e) {
|
||||
if (this.active) {
|
||||
if (!this.$refs.activator.$el.contains(e.target) && !this.scrolling) {
|
||||
this.closeMenu()
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* data utils methods
|
||||
*/
|
||||
patternMatched (value) {
|
||||
try {
|
||||
@ -268,6 +298,17 @@ export default {
|
||||
return this.valueOptionMap.get(value) || null
|
||||
}
|
||||
},
|
||||
isSelected (option) {
|
||||
if (this.multiple) {
|
||||
if (!Array.isArray(this.value)) return false
|
||||
return 1 + this.value.findIndex(value => value === option.value)
|
||||
} else {
|
||||
return option.value === this.value
|
||||
}
|
||||
},
|
||||
/**
|
||||
* event utils methods
|
||||
*/
|
||||
emitChangeEvent (newValue) {
|
||||
if (this.emitOption) {
|
||||
if (this.multiple) {
|
||||
@ -285,45 +326,7 @@ export default {
|
||||
this.$emit('change', newValue)
|
||||
}
|
||||
},
|
||||
isSelected (option) {
|
||||
if (this.multiple) {
|
||||
if (!Array.isArray(this.value)) return false
|
||||
return 1 + this.value.findIndex(value => value === option.value)
|
||||
} else {
|
||||
return option.value === this.value
|
||||
}
|
||||
},
|
||||
handleClickOutsideMenu (e) {
|
||||
if (this.active) {
|
||||
if (!this.$refs.activator.$el.contains(e.target) && !this.scrolling) {
|
||||
console.log('handleClickOutsideMenu')
|
||||
this.closeMenu()
|
||||
}
|
||||
}
|
||||
},
|
||||
openMenu () {
|
||||
console.log('openMenu')
|
||||
this.pattern = ''
|
||||
this.activate()
|
||||
if (this.filterable) {
|
||||
this.$refs.activator.focusPatternInput()
|
||||
}
|
||||
},
|
||||
closeMenu () {
|
||||
console.log('closeMenu')
|
||||
this.deactivate()
|
||||
},
|
||||
handleActivatorClick () {
|
||||
if (this.disabled) return
|
||||
if (!this.active) {
|
||||
this.openMenu()
|
||||
} else {
|
||||
if (!this.filterable) {
|
||||
this.closeMenu()
|
||||
}
|
||||
}
|
||||
},
|
||||
toggleOption (option) {
|
||||
handleToggleOption (option) {
|
||||
if (this.disabled) return
|
||||
if (this.multiple) {
|
||||
if (this.remote) {
|
||||
@ -337,10 +340,8 @@ export default {
|
||||
const index = newValue.findIndex(value => value === option.value)
|
||||
if (~index) {
|
||||
newValue.splice(index, 1)
|
||||
// this.emitChangeEvent(item, false)
|
||||
} else {
|
||||
newValue.push(option.value)
|
||||
// this.emitChangeEvent(item, true)
|
||||
this.pattern = ''
|
||||
}
|
||||
this.$emit('input', newValue)
|
||||
@ -354,17 +355,6 @@ export default {
|
||||
this.closeMenu()
|
||||
}
|
||||
},
|
||||
handleMenuScrollStart () {
|
||||
this.scrolling = true
|
||||
},
|
||||
handleMenuScrollEnd () {
|
||||
window.setTimeout(() => {
|
||||
this.scrolling = false
|
||||
}, 0)
|
||||
},
|
||||
handleMenuScroll (e, scrollContainer, scrollContent) {
|
||||
this.$emit('scroll', e, scrollContainer, scrollContent)
|
||||
},
|
||||
handleDeleteLastOption (e) {
|
||||
if (!this.pattern.length) {
|
||||
const newValue = this.value
|
||||
@ -380,11 +370,34 @@ export default {
|
||||
this.onSearch(e.target.value)
|
||||
}
|
||||
},
|
||||
handleClear (e) {
|
||||
e.stopPropagation()
|
||||
this.closeMenu()
|
||||
this.$emit('input', null)
|
||||
this.emitChangeEvent(null)
|
||||
},
|
||||
/**
|
||||
* scroll events on menu
|
||||
*/
|
||||
handleMenuScrollStart () {
|
||||
this.scrolling = true
|
||||
},
|
||||
handleMenuScrollEnd () {
|
||||
window.setTimeout(() => {
|
||||
this.scrolling = false
|
||||
}, 0)
|
||||
},
|
||||
handleMenuScroll (e, scrollContainer, scrollContent) {
|
||||
this.$emit('scroll', e, scrollContainer, scrollContent)
|
||||
},
|
||||
/**
|
||||
* keyboard events
|
||||
*/
|
||||
handleKeyUpEnter (e) {
|
||||
if (this.active) {
|
||||
const pendingOption = this.$refs.contentInner && this.$refs.contentInner.pendingOption
|
||||
if (pendingOption) {
|
||||
this.toggleOption(pendingOption)
|
||||
this.handleToggleOption(pendingOption)
|
||||
} else {
|
||||
this.closeMenu()
|
||||
}
|
||||
@ -394,17 +407,17 @@ export default {
|
||||
e.preventDefault()
|
||||
},
|
||||
handleKeyUpSpace (e) {
|
||||
this.handleKeyUpEnter(e)
|
||||
if (!this.filterable) {
|
||||
this.handleKeyUpEnter(e)
|
||||
}
|
||||
},
|
||||
handleKeyUpUp () {
|
||||
// console.log('keyup up')
|
||||
if (this.loading) return
|
||||
if (this.active) {
|
||||
this.$refs.contentInner.prev()
|
||||
}
|
||||
},
|
||||
handleKeyUpDown () {
|
||||
// console.log('keyup down')
|
||||
if (this.loading) return
|
||||
if (this.active) {
|
||||
this.$refs.contentInner.next()
|
||||
@ -415,14 +428,11 @@ export default {
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
handleClear (e) {
|
||||
// e.stopPropagation()
|
||||
this.closeMenu()
|
||||
this.$emit('input', null)
|
||||
this.emitChangeEvent(null)
|
||||
},
|
||||
handlePickerBlur () {
|
||||
handleKeyUpEsc (e) {
|
||||
this.closeMenu()
|
||||
this.$nextTick().then(() => {
|
||||
this.$refs.activator.focusPatternInputWrapper()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
export default function processedOptions (options) {
|
||||
export default function linkedOptions (options) {
|
||||
const decoratedOptions = cloneDeep(options).map((option, index) => {
|
||||
return {
|
||||
...option,
|
@ -2,7 +2,6 @@
|
||||
@import './theme/default.scss';
|
||||
|
||||
@include b(base-picker) {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
box-shadow: none;
|
||||
border-radius: $select-border-radius;
|
||||
@ -24,7 +23,7 @@
|
||||
}
|
||||
.n-base-picker-label, .n-base-picker-tags {
|
||||
cursor: pointer;
|
||||
// outline: none;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
transition: box-shadow .3s $default-cubic-bezier, background-color .3s $default-cubic-bezier;
|
||||
@ -33,6 +32,7 @@
|
||||
}
|
||||
.n-base-picker-label {
|
||||
.n-base-picker-label__input {
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@ -180,6 +180,9 @@
|
||||
}
|
||||
.n-base-picker-label__input {
|
||||
color: rgba(255, 255, 255, 0.20);
|
||||
&.n-base-picker-label__input--placeholder {
|
||||
color: rgba(255, 255, 255, 0.20);
|
||||
}
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.20);
|
||||
}
|
||||
@ -201,6 +204,7 @@
|
||||
}
|
||||
|
||||
@include b(base-picker-input-tag) {
|
||||
outline: none;
|
||||
position: relative;
|
||||
margin-bottom: 4px;
|
||||
display: inline-block;
|
||||
@ -209,7 +213,7 @@
|
||||
.n-base-picker-input-tag__input {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
// outline: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
max-width: 100%;
|
||||
caret-color: $input-caret-color;
|
||||
|
Loading…
Reference in New Issue
Block a user