diff --git a/components/_util/props-util.js b/components/_util/props-util.js
index b3912d022..d9bde0387 100644
--- a/components/_util/props-util.js
+++ b/components/_util/props-util.js
@@ -52,9 +52,11 @@ const getSlots = (ele) => {
const children = ele.children || componentOptions.children || []
const slots = {}
children.forEach(child => {
- const name = (child.data && child.data.slot) || 'default'
- slots[name] = slots[name] || []
- slots[name].push(child)
+ if (!isEmptyElement(child)) {
+ const name = (child.data && child.data.slot) || 'default'
+ slots[name] = slots[name] || []
+ slots[name].push(child)
+ }
})
return slots
}
@@ -213,12 +215,12 @@ export function getComponentName (opts) {
return opts && (opts.Ctor.options.name || opts.tag)
}
-export function isEmptyElement (ele) {
- return !(ele.tag || ele.text.trim() !== '')
+export function isEmptyElement (c) {
+ return !(c.tag || (c.text && c.text.trim() !== ''))
}
export function filterEmpty (children = []) {
- return children.filter(c => c.tag || (c.text && c.text.trim() !== ''))
+ return children.filter(c => !isEmptyElement(c))
}
const initDefaultProps = (propTypes, defaultProps) => {
Object.keys(defaultProps).forEach(k => {
diff --git a/components/vc-select/DropdownMenu.jsx b/components/vc-select/DropdownMenu.jsx
index 66ace3f4a..667b39418 100644
--- a/components/vc-select/DropdownMenu.jsx
+++ b/components/vc-select/DropdownMenu.jsx
@@ -11,6 +11,7 @@ export default {
name: 'DropdownMenu',
mixins: [BaseMixin],
props: {
+ ariaId: PropTypes.string,
defaultActiveFirstOption: PropTypes.bool,
value: PropTypes.any,
dropdownMenuStyle: PropTypes.object,
@@ -28,8 +29,10 @@ export default {
menuItemSelectedIcon: PropTypes.any,
},
- beforeMount () {
+ created () {
+ this.rafInstance = { cancel: () => null }
this.lastInputValue = this.$props.inputValue
+ this.lastVisible = false
},
mounted () {
@@ -101,6 +104,7 @@ export default {
firstActiveValue,
dropdownMenuStyle,
backfillValue,
+ visible,
} = props
const menuItemSelectedIcon = getComponentFromProp(this, 'menuItemSelectedIcon')
const { menuDeselect, menuSelect, popupScroll } = this.$listeners
@@ -136,6 +140,8 @@ export default {
if (selectedKeys.length || firstActiveValue) {
if (props.visible && !this.lastVisible) {
activeKeyProps.activeKey = selectedKeys[0] !== undefined ? selectedKeys[0] : firstActiveValue
+ } else if (!visible) {
+ activeKeyProps.activeKey = undefined
}
let foundFirst = false
// set firstActiveItem via cloning menus
@@ -143,9 +149,7 @@ export default {
const clone = item => {
if (
(!foundFirst && selectedKeys.indexOf(item.key) !== -1) ||
- (!foundFirst &&
- !selectedKeys.length &&
- firstActiveValue.indexOf(item.key) !== -1)
+ (!foundFirst && !selectedKeys.length && firstActiveValue.indexOf(item.key) !== -1)
) {
foundFirst = true
return cloneElement(item, {
@@ -198,6 +202,8 @@ export default {
overflow: 'auto',
transform: 'translateZ(0)',
}}
+ id={this.$props.ariaId}
+ tabIndex='-1'
onFocus={popupFocus}
onMousedown={preventDefaultEvent}
onScroll={popupScroll}
diff --git a/components/vc-select/OptGroup.jsx b/components/vc-select/OptGroup.jsx
index 226ac6118..acf156a21 100644
--- a/components/vc-select/OptGroup.jsx
+++ b/components/vc-select/OptGroup.jsx
@@ -2,7 +2,14 @@
import PropTypes from '../_util/vue-types'
export default {
props: {
- label: PropTypes.any,
+ value: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ label: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
},
isSelectOptGroup: true,
}
diff --git a/components/vc-select/Option.jsx b/components/vc-select/Option.jsx
index e683b6e93..2fe61014b 100644
--- a/components/vc-select/Option.jsx
+++ b/components/vc-select/Option.jsx
@@ -7,8 +7,15 @@ export default {
PropTypes.string,
PropTypes.number,
]),
+ label: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
disabled: PropTypes.bool,
- title: PropTypes.string,
+ title: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
},
isSelectOption: true,
}
diff --git a/components/vc-select/PropTypes.js b/components/vc-select/PropTypes.js
index 37b2d62a6..c095dac8e 100644
--- a/components/vc-select/PropTypes.js
+++ b/components/vc-select/PropTypes.js
@@ -31,6 +31,7 @@ export const SelectPropTypes = {
placeholder: PropTypes.any,
// onDeselect: PropTypes.func,
labelInValue: PropTypes.bool,
+ loading: PropTypes.bool,
value: PropTypes.any,
defaultValue: PropTypes.any,
dropdownStyle: PropTypes.object,
@@ -47,4 +48,12 @@ export const SelectPropTypes = {
inputIcon: PropTypes.any,
removeIcon: PropTypes.any,
menuItemSelectedIcon: PropTypes.any,
+ dropdownRender: PropTypes.func,
+ mode: PropTypes.oneOf(['multiple', 'tags']),
+ backfill: PropTypes.bool,
+ dropdownAlign: PropTypes.any,
+ dropdownMatchSelectWidth: PropTypes.bool,
+ dropdownMenuStyle: PropTypes.object,
+ notFoundContent: PropTypes.oneOfType([String, Number]),
+ tabIndex: PropTypes.oneOfType([String, Number]),
}
diff --git a/components/vc-select/Select.jsx b/components/vc-select/Select.jsx
index 33b1aeabb..a443d28fa 100644
--- a/components/vc-select/Select.jsx
+++ b/components/vc-select/Select.jsx
@@ -7,56 +7,58 @@ import { Item as MenuItem, ItemGroup as MenuItemGroup } from '../vc-menu'
import warning from 'warning'
import Vue from 'vue'
import Option from './Option'
-import { hasProp, getSlotOptions, getPropsData, getValueByProp as getValue, getComponentFromProp, getEvents, getClass, getStyle, getAttrs, getOptionProps } from '../_util/props-util'
+import OptGroup from './OptGroup'
+import { hasProp, getSlotOptions, getPropsData, getValueByProp as getValue, getComponentFromProp, getEvents, getClass, getStyle, getAttrs, getOptionProps, getSlots } from '../_util/props-util'
import getTransitionProps from '../_util/getTransitionProps'
import { cloneElement } from '../_util/vnode'
import BaseMixin from '../_util/BaseMixin'
import proxyComponent from '../_util/proxyComponent'
import ref from 'vue-ref'
-
-Vue.use(ref, { name: 'ant-ref' })
-
+import SelectTrigger from './SelectTrigger'
import {
+ defaultFilterFn,
+ findFirstMenuItem,
+ findIndexInValueBySingleValue,
+ generateUUID,
+ getLabelFromPropsValue,
+ getMapKey,
getPropValue,
getValuePropValue,
+ includesSeparators,
isCombobox,
isMultipleOrTags,
isMultipleOrTagsOrCombobox,
isSingleMode,
+ preventDefaultEvent,
+ saveRef,
+ splitBySeparators,
toArray,
- getMapKey,
- findIndexInValueBySingleValue,
- getLabelFromPropsValue,
+ toTitle,
UNSELECTABLE_ATTRIBUTE,
UNSELECTABLE_STYLE,
- preventDefaultEvent,
- findFirstMenuItem,
- includesSeparators,
- splitBySeparators,
- defaultFilterFn,
validateOptionValue,
- saveRef,
- toTitle,
} from './util'
-import SelectTrigger from './SelectTrigger'
import { SelectPropTypes } from './PropTypes'
+Vue.use(ref, { name: 'ant-ref' })
const SELECT_EMPTY_VALUE_KEY = 'RC_SELECT_EMPTY_VALUE_KEY'
-function noop () {}
+const noop = () => null
function chaining (...fns) {
return function (...args) { // eslint-disable-line
// eslint-disable-line
for (let i = 0; i < fns.length; i++) {
if (fns[i] && typeof fns[i] === 'function') {
- fns[i].apply(this, args)
+ fns[i].apply(chaining, args)
}
}
}
}
const Select = {
inheritAttrs: false,
+ Option,
+ OptGroup,
name: 'Select',
mixins: [BaseMixin],
props: {
@@ -80,6 +82,8 @@ const Select = {
combobox: PropTypes.bool.def(false),
tokenSeparators: PropTypes.arrayOf(PropTypes.string).def([]),
autoClearSearchValue: PropTypes.bool.def(true),
+ tabIndex: PropTypes.any.def(0),
+ dropdownRender: PropTypes.func.def(menu => menu),
// onChange: noop,
// onFocus: noop,
// onBlur: noop,
@@ -99,6 +103,10 @@ const Select = {
this.saveSelectTriggerRef = saveRef(this, 'selectTriggerRef')
this.saveRootRef = saveRef(this, 'rootRef')
this.saveSelectionRef = saveRef(this, 'selectionRef')
+ this.ariaId = generateUUID()
+ this._focused = false
+ this._mouseDown = false
+ this._options = []
},
data () {
const props = getOptionProps(this)
@@ -116,6 +124,7 @@ const Select = {
) : '',
_open: props.defaultOpen,
_optionsInfo: optionsInfo,
+ _backfillValue: '',
// a flag for aviod redundant getOptionsInfoFromProps call
_skipBuildOptionsInfo: true,
}
@@ -140,10 +149,10 @@ const Select = {
if (isMultipleOrTags(this.$props)) {
const inputNode = this.getInputDOMNode()
const mirrorNode = this.getInputMirrorDOMNode()
- if (inputNode.value) {
+ if (inputNode.value && inputNode.value && mirrorNode) {
inputNode.style.width = ''
inputNode.style.width = `${mirrorNode.clientWidth + 10}px`
- } else {
+ } else if (inputNode) {
inputNode.style.width = ''
}
}
@@ -185,6 +194,91 @@ const Select = {
}
return newState
},
+ getOptionsFromChildren (children = [], options = []) {
+ children.forEach(child => {
+ if (!child.data || child.data.slot !== undefined) {
+ return
+ }
+ if (getSlotOptions(child).isSelectOptGroup) {
+ this.getOptionsFromChildren(child.componentOptions.children, options)
+ } else {
+ options.push(child)
+ }
+ })
+ return options
+ },
+ getInputValueForCombobox (props, optionsInfo, useDefaultValue) {
+ let value = []
+ if ('value' in props && !useDefaultValue) {
+ value = toArray(props.value)
+ }
+ if ('defaultValue' in props && useDefaultValue) {
+ value = toArray(props.defaultValue)
+ }
+ if (value.length) {
+ value = value[0]
+ } else {
+ return ''
+ }
+ let label = value
+ if (props.labelInValue) {
+ label = value.label
+ } else if (optionsInfo[getMapKey(value)]) {
+ label = optionsInfo[getMapKey(value)].label
+ }
+ if (label === undefined) {
+ label = ''
+ }
+ return label
+ },
+
+ getLabelFromOption (props, option) {
+ return getPropValue(option, props.optionLabelProp)
+ },
+
+ getOptionsInfoFromProps (props, preState) {
+ const options = this.getOptionsFromChildren(this.$props.children)
+ const optionsInfo = {}
+ options.forEach((option) => {
+ const singleValue = getValuePropValue(option)
+ optionsInfo[getMapKey(singleValue)] = {
+ option,
+ value: singleValue,
+ label: this.getLabelFromOption(props, option),
+ title: getValue(option, 'title'),
+ }
+ })
+ if (preState) {
+ // keep option info in pre state value.
+ const oldOptionsInfo = preState._optionsInfo
+ const value = preState._value
+ if (value) {
+ value.forEach(v => {
+ const key = getMapKey(v)
+ if (!optionsInfo[key] && oldOptionsInfo[key] !== undefined) {
+ optionsInfo[key] = oldOptionsInfo[key]
+ }
+ })
+ }
+ }
+ return optionsInfo
+ },
+
+ getValueFromProps (props, useDefaultValue) {
+ let value = []
+ if ('value' in props && !useDefaultValue) {
+ value = toArray(props.value)
+ }
+ if ('defaultValue' in props && useDefaultValue) {
+ value = toArray(props.defaultValue)
+ }
+ if (props.labelInValue) {
+ value = value.map((v) => {
+ return v.key
+ })
+ }
+ return value
+ },
onInputChange (event) {
const { tokenSeparators } = this.$props
@@ -290,7 +384,7 @@ const Select = {
return
}
- if (this.getRealOpenState(state)) {
+ if (this.getRealOpenState(state) && this.selectTriggerRef) {
const menu = this.selectTriggerRef.getInnerMenu()
if (menu && menu.onKeyDown(event, this.handleBackfill)) {
event.preventDefault()
@@ -324,12 +418,8 @@ const Select = {
this.setOpenState(false, true)
}
this.fireChange(value)
- let inputValue
- if (isCombobox(props)) {
- inputValue = getPropValue(item, props.optionLabelProp)
- } else {
- inputValue = ''
- }
+ const inputValue = isCombobox(props) ? getPropValue(item, props.optionLabelProp) : ''
+
if (props.autoClearSearchValue) {
this.setInputValue(inputValue, false)
}
@@ -357,7 +447,7 @@ const Select = {
},
onPlaceholderClick (e) {
- if (this.getInputDOMNode()) {
+ if (this.getInputDOMNode() && this.getInputDOMNode()) {
this.getInputDOMNode().focus()
}
},
@@ -389,89 +479,6 @@ const Select = {
onChoiceAnimationLeave () {
this.forcePopupAlign()
},
- getOptionsFromChildren (children = [], options = []) {
- children.forEach(child => {
- if (!child.data || child.data.slot !== undefined) {
- return
- }
- if (getSlotOptions(child).isSelectOptGroup) {
- this.getOptionsFromChildren(child.componentOptions.children, options)
- } else {
- options.push(child)
- }
- })
- return options
- },
- getInputValueForCombobox (props, optionsInfo, useDefaultValue) {
- let value = []
- if ('value' in props && !useDefaultValue) {
- value = toArray(props.value)
- }
- if ('defaultValue' in props && useDefaultValue) {
- value = toArray(props.defaultValue)
- }
- if (value.length) {
- value = value[0]
- } else {
- return ''
- }
- let label = value
- if (props.labelInValue) {
- label = value.label
- } else if (optionsInfo[getMapKey(value)]) {
- label = optionsInfo[getMapKey(value)].label
- }
- if (label === undefined) {
- label = ''
- }
- return label
- },
-
- getLabelFromOption (props, option) {
- return getPropValue(option, props.optionLabelProp)
- },
-
- getOptionsInfoFromProps (props, preState) {
- const options = this.getOptionsFromChildren(this.$props.children)
- const optionsInfo = {}
- options.forEach((option) => {
- const singleValue = getValuePropValue(option)
- optionsInfo[getMapKey(singleValue)] = {
- option,
- value: singleValue,
- label: this.getLabelFromOption(props, option),
- title: getValue(option, 'title'),
- }
- })
- if (preState) {
- // keep option info in pre state value.
- const oldOptionsInfo = preState._optionsInfo
- const value = preState._value
- value.forEach(v => {
- const key = getMapKey(v)
- if (!optionsInfo[key] && oldOptionsInfo[key] !== undefined) {
- optionsInfo[key] = oldOptionsInfo[key]
- }
- })
- }
- return optionsInfo
- },
-
- getValueFromProps (props, useDefaultValue) {
- let value = []
- if ('value' in props && !useDefaultValue) {
- value = toArray(props.value)
- }
- if ('defaultValue' in props && useDefaultValue) {
- value = toArray(props.defaultValue)
- }
- if (props.labelInValue) {
- value = value.map((v) => {
- return v.key
- })
- }
- return value
- },
getOptionInfoBySingleValue (value, optionsInfo) {
let info
@@ -515,7 +522,8 @@ const Select = {
let value = null
Object.keys(this.$data._optionsInfo).forEach(key => {
const info = this.$data._optionsInfo[key]
- if (toArray(info.label).join('') === label) {
+ const oldLable = toArray(info.label)
+ if (oldLable && oldLable.join('') === label) {
value = info.value
}
})
@@ -532,8 +540,8 @@ const Select = {
return value
},
- getVLForOnChange (vls_) {
- let vls = vls_
+ getVLForOnChange (vlsS) {
+ let vls = vlsS
if (vls !== undefined) {
if (!this.labelInValue) {
vls = vls.map(v => v)
@@ -567,10 +575,11 @@ const Select = {
if (state._inputValue) {
hidden = true
}
- if (state._value.length) {
+ const value = state._value
+ if (value.length) {
hidden = true
}
- if (isCombobox(props) && state._value.length === 1 && !state._value[0]) {
+ if (isCombobox(props) && value.length === 1 && (state._value && !state._value[0])) {
hidden = false
}
const placeholder = props.placeholder
@@ -634,10 +643,16 @@ const Select = {
this.setInputValue('')
} else {
// why not use setState?
- this.$data._inputValue = this.getInputDOMNode().value = ''
+ this.$data._inputValue = ''
+ this.$nextTick(() => {
+ if (this.getInputDOMNode && this.getInputDOMNode()) {
+ this.getInputDOMNode().value = ''
+ }
+ })
}
- value = this.getValueByInput(inputValue)
- if (value !== undefined) {
+ const tmpValue = this.getValueByInput(inputValue)
+ if (tmpValue !== undefined) {
+ value = tmpValue
this.fireChange(value)
}
}
@@ -677,9 +692,11 @@ const Select = {
const props = this.$props
const { _inputValue: inputValue } = this.$data
const attrs = getAttrs(this)
+ const defaultInput =
+
const inputElement = props.getInputElement
? props.getInputElement()
- :
+ : defaultInput
const inputCls = classnames(getClass(inputElement), {
[`${props.prefixCls}-search__field`]: true,
})
@@ -749,34 +766,38 @@ const Select = {
},
getPopupDOMNode () {
- return this.selectTriggerRef.getPopupDOMNode()
+ if (this.selectTriggerRef) {
+ return this.selectTriggerRef.getPopupDOMNode()
+ }
},
getPopupMenuComponent () {
- return this.selectTriggerRef.getInnerMenu()
+ if (this.selectTriggerRef) {
+ return this.selectTriggerRef.getInnerMenu()
+ }
},
setOpenState (open, needFocus) {
const { $props: props, $data: state } = this
if (state._open === open) {
- this.maybeFocus(open, needFocus)
+ this.maybeFocus(open, !!needFocus)
return
}
this.__emit('dropdownVisibleChange', open)
const nextState = {
_open: open,
- _backfillValue: undefined,
+ _backfillValue: '',
}
// clear search input value when open is false in singleMode.
if (!open && isSingleMode(props) && props.showSearch) {
this.setInputValue('', false)
}
if (!open) {
- this.maybeFocus(open, needFocus)
+ this.maybeFocus(open, !!needFocus)
}
this.setState(nextState, () => {
if (open) {
- this.maybeFocus(open, needFocus)
+ this.maybeFocus(open, !!needFocus)
}
})
},
@@ -791,11 +812,11 @@ const Select = {
}
}
},
- getValueByInput (string) {
+ getValueByInput (str) {
const { multiple, tokenSeparators } = this.$props
let nextValue = this.$data._value
let hasNewValue = false
- splitBySeparators(string, tokenSeparators).forEach(label => {
+ splitBySeparators(str, tokenSeparators).forEach(label => {
const selectedValue = [label]
if (multiple) {
const value = this.getValueByLabel(label)
@@ -804,13 +825,10 @@ const Select = {
hasNewValue = true
this.fireSelect(value)
}
- } else {
- // tag
- if (findIndexInValueBySingleValue(nextValue, label) === -1) {
- nextValue = nextValue.concat(selectedValue)
- hasNewValue = true
- this.fireSelect(label)
- }
+ } else if (findIndexInValueBySingleValue(nextValue, label) === -1) {
+ nextValue = nextValue.concat(selectedValue)
+ hasNewValue = true
+ this.fireSelect(label)
}
})
return hasNewValue ? nextValue : undefined
@@ -833,17 +851,17 @@ const Select = {
},
focus () {
- if (isSingleMode(this.$props)) {
+ if (isSingleMode(this.$props) && this.selectionRef) {
this.selectionRef.focus()
- } else {
+ } else if (this.getInputDOMNode()) {
this.getInputDOMNode().focus()
}
},
blur () {
- if (isSingleMode(this.$props)) {
+ if (isSingleMode(this.$props) && this.selectionRef) {
this.selectionRef.blur()
- } else {
+ } else if (this.getInputDOMNode()) {
this.getInputDOMNode().blur()
}
},
@@ -880,11 +898,11 @@ const Select = {
}
let filterFn = this.$props.filterOption
if (hasProp(this, 'filterOption')) {
- if (this.filterOption === true) {
- filterFn = defaultFilter
+ if (filterFn === true) {
+ filterFn = defaultFilter.bind(this)
}
} else {
- filterFn = defaultFilter
+ filterFn = defaultFilter.bind(this)
}
if (!filterFn) {
return true
@@ -900,7 +918,7 @@ const Select = {
if (this.focusTimer) {
this.clearFocusTime()
}
- this.focusTimer = setTimeout(() => {
+ this.focusTimer = window.setTimeout(() => {
// this._focused = true
// this.updateFocusClassName()
this.$emit('focus')
@@ -940,7 +958,7 @@ const Select = {
input.focus()
this._focused = true
}
- } else if (activeElement !== this.selectionRef) {
+ } else if (activeElement !== this.selectionRef && this.selectionRef) {
this.selectionRef.focus()
this._focused = true
}
@@ -956,8 +974,8 @@ const Select = {
if (e && e.stopPropagation) {
e.stopPropagation()
}
-
- const value = this.$data._value.filter(singleValue => {
+ const oldValue = this.$data._value
+ const value = oldValue.filter(singleValue => {
return singleValue !== selectedKey
})
const canMultiple = isMultipleOrTags(props)
@@ -1006,7 +1024,9 @@ const Select = {
if (!this.$data._open) {
return
}
- this.selectTriggerRef && this.selectTriggerRef.triggerRef.forcePopupAlign()
+ if (this.selectTriggerRef && this.selectTriggerRef.triggerRef) {
+ this.selectTriggerRef.triggerRef.forcePopupAlign()
+ }
},
renderFilterOptions () {
const { _inputValue: inputValue } = this.$data
@@ -1024,8 +1044,7 @@ const Select = {
value = value.filter(singleValue => {
return (
childrenKeys.indexOf(singleValue) === -1 &&
- (!inputValue ||
- String(singleValue).indexOf(String(inputValue)) > -1)
+ (!inputValue || String(singleValue).indexOf(String(inputValue)) > -1)
)
})
value.forEach(singleValue => {
@@ -1109,25 +1128,49 @@ const Select = {
return
}
if (getSlotOptions(child).isSelectOptGroup) {
- const innerItems = this.renderFilterOptionsFromChildren(
- child.componentOptions.children,
- childrenKeys,
- menuItems,
- )
- if (innerItems.length) {
- let label = getComponentFromProp(child, 'label')
- let key = child.key
- if (!key && typeof label === 'string') {
- key = label
- } else if (!label && key) {
- label = key
- }
+ let label = getComponentFromProp(child, 'label')
+ let key = child.key
+ if (!key && typeof label === 'string') {
+ key = label
+ } else if (!label && key) {
+ label = key
+ }
+ const childChildren = getSlots(child).default
+ // Match option group label
+ if (inputValue && this._filterOption(inputValue, child)) {
+ const innerItems = childChildren.map(
+ (subChild) => {
+ const childValueSub = getValuePropValue(subChild) || subChild.key
+ return (
+
+ )
+ },
+ )
+
sel.push(