refactor(select): support main keyboard events

This commit is contained in:
07akioni 2019-08-26 16:25:51 +08:00
parent 6bba332083
commit 74eb78fd16
6 changed files with 113 additions and 97 deletions

View File

@ -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')

View File

@ -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

View File

@ -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)

View File

@ -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()
})
}
}
}

View File

@ -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,

View File

@ -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;