feat: update vc-select

This commit is contained in:
tangjinzhou 2019-01-04 23:00:09 +08:00
parent e706170669
commit 435b8a19e6
9 changed files with 386 additions and 235 deletions

View File

@ -52,9 +52,11 @@ const getSlots = (ele) => {
const children = ele.children || componentOptions.children || [] const children = ele.children || componentOptions.children || []
const slots = {} const slots = {}
children.forEach(child => { children.forEach(child => {
const name = (child.data && child.data.slot) || 'default' if (!isEmptyElement(child)) {
slots[name] = slots[name] || [] const name = (child.data && child.data.slot) || 'default'
slots[name].push(child) slots[name] = slots[name] || []
slots[name].push(child)
}
}) })
return slots return slots
} }
@ -213,12 +215,12 @@ export function getComponentName (opts) {
return opts && (opts.Ctor.options.name || opts.tag) return opts && (opts.Ctor.options.name || opts.tag)
} }
export function isEmptyElement (ele) { export function isEmptyElement (c) {
return !(ele.tag || ele.text.trim() !== '') return !(c.tag || (c.text && c.text.trim() !== ''))
} }
export function filterEmpty (children = []) { 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) => { const initDefaultProps = (propTypes, defaultProps) => {
Object.keys(defaultProps).forEach(k => { Object.keys(defaultProps).forEach(k => {

View File

@ -11,6 +11,7 @@ export default {
name: 'DropdownMenu', name: 'DropdownMenu',
mixins: [BaseMixin], mixins: [BaseMixin],
props: { props: {
ariaId: PropTypes.string,
defaultActiveFirstOption: PropTypes.bool, defaultActiveFirstOption: PropTypes.bool,
value: PropTypes.any, value: PropTypes.any,
dropdownMenuStyle: PropTypes.object, dropdownMenuStyle: PropTypes.object,
@ -28,8 +29,10 @@ export default {
menuItemSelectedIcon: PropTypes.any, menuItemSelectedIcon: PropTypes.any,
}, },
beforeMount () { created () {
this.rafInstance = { cancel: () => null }
this.lastInputValue = this.$props.inputValue this.lastInputValue = this.$props.inputValue
this.lastVisible = false
}, },
mounted () { mounted () {
@ -101,6 +104,7 @@ export default {
firstActiveValue, firstActiveValue,
dropdownMenuStyle, dropdownMenuStyle,
backfillValue, backfillValue,
visible,
} = props } = props
const menuItemSelectedIcon = getComponentFromProp(this, 'menuItemSelectedIcon') const menuItemSelectedIcon = getComponentFromProp(this, 'menuItemSelectedIcon')
const { menuDeselect, menuSelect, popupScroll } = this.$listeners const { menuDeselect, menuSelect, popupScroll } = this.$listeners
@ -136,6 +140,8 @@ export default {
if (selectedKeys.length || firstActiveValue) { if (selectedKeys.length || firstActiveValue) {
if (props.visible && !this.lastVisible) { if (props.visible && !this.lastVisible) {
activeKeyProps.activeKey = selectedKeys[0] !== undefined ? selectedKeys[0] : firstActiveValue activeKeyProps.activeKey = selectedKeys[0] !== undefined ? selectedKeys[0] : firstActiveValue
} else if (!visible) {
activeKeyProps.activeKey = undefined
} }
let foundFirst = false let foundFirst = false
// set firstActiveItem via cloning menus // set firstActiveItem via cloning menus
@ -143,9 +149,7 @@ export default {
const clone = item => { const clone = item => {
if ( if (
(!foundFirst && selectedKeys.indexOf(item.key) !== -1) || (!foundFirst && selectedKeys.indexOf(item.key) !== -1) ||
(!foundFirst && (!foundFirst && !selectedKeys.length && firstActiveValue.indexOf(item.key) !== -1)
!selectedKeys.length &&
firstActiveValue.indexOf(item.key) !== -1)
) { ) {
foundFirst = true foundFirst = true
return cloneElement(item, { return cloneElement(item, {
@ -198,6 +202,8 @@ export default {
overflow: 'auto', overflow: 'auto',
transform: 'translateZ(0)', transform: 'translateZ(0)',
}} }}
id={this.$props.ariaId}
tabIndex='-1'
onFocus={popupFocus} onFocus={popupFocus}
onMousedown={preventDefaultEvent} onMousedown={preventDefaultEvent}
onScroll={popupScroll} onScroll={popupScroll}

View File

@ -2,7 +2,14 @@
import PropTypes from '../_util/vue-types' import PropTypes from '../_util/vue-types'
export default { export default {
props: { props: {
label: PropTypes.any, value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
label: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
}, },
isSelectOptGroup: true, isSelectOptGroup: true,
} }

View File

@ -7,8 +7,15 @@ export default {
PropTypes.string, PropTypes.string,
PropTypes.number, PropTypes.number,
]), ]),
label: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
disabled: PropTypes.bool, disabled: PropTypes.bool,
title: PropTypes.string, title: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
}, },
isSelectOption: true, isSelectOption: true,
} }

View File

@ -31,6 +31,7 @@ export const SelectPropTypes = {
placeholder: PropTypes.any, placeholder: PropTypes.any,
// onDeselect: PropTypes.func, // onDeselect: PropTypes.func,
labelInValue: PropTypes.bool, labelInValue: PropTypes.bool,
loading: PropTypes.bool,
value: PropTypes.any, value: PropTypes.any,
defaultValue: PropTypes.any, defaultValue: PropTypes.any,
dropdownStyle: PropTypes.object, dropdownStyle: PropTypes.object,
@ -47,4 +48,12 @@ export const SelectPropTypes = {
inputIcon: PropTypes.any, inputIcon: PropTypes.any,
removeIcon: PropTypes.any, removeIcon: PropTypes.any,
menuItemSelectedIcon: 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]),
} }

View File

@ -7,56 +7,58 @@ import { Item as MenuItem, ItemGroup as MenuItemGroup } from '../vc-menu'
import warning from 'warning' import warning from 'warning'
import Vue from 'vue' import Vue from 'vue'
import Option from './Option' 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 getTransitionProps from '../_util/getTransitionProps'
import { cloneElement } from '../_util/vnode' import { cloneElement } from '../_util/vnode'
import BaseMixin from '../_util/BaseMixin' import BaseMixin from '../_util/BaseMixin'
import proxyComponent from '../_util/proxyComponent' import proxyComponent from '../_util/proxyComponent'
import ref from 'vue-ref' import ref from 'vue-ref'
import SelectTrigger from './SelectTrigger'
Vue.use(ref, { name: 'ant-ref' })
import { import {
defaultFilterFn,
findFirstMenuItem,
findIndexInValueBySingleValue,
generateUUID,
getLabelFromPropsValue,
getMapKey,
getPropValue, getPropValue,
getValuePropValue, getValuePropValue,
includesSeparators,
isCombobox, isCombobox,
isMultipleOrTags, isMultipleOrTags,
isMultipleOrTagsOrCombobox, isMultipleOrTagsOrCombobox,
isSingleMode, isSingleMode,
preventDefaultEvent,
saveRef,
splitBySeparators,
toArray, toArray,
getMapKey, toTitle,
findIndexInValueBySingleValue,
getLabelFromPropsValue,
UNSELECTABLE_ATTRIBUTE, UNSELECTABLE_ATTRIBUTE,
UNSELECTABLE_STYLE, UNSELECTABLE_STYLE,
preventDefaultEvent,
findFirstMenuItem,
includesSeparators,
splitBySeparators,
defaultFilterFn,
validateOptionValue, validateOptionValue,
saveRef,
toTitle,
} from './util' } from './util'
import SelectTrigger from './SelectTrigger'
import { SelectPropTypes } from './PropTypes' import { SelectPropTypes } from './PropTypes'
Vue.use(ref, { name: 'ant-ref' })
const SELECT_EMPTY_VALUE_KEY = 'RC_SELECT_EMPTY_VALUE_KEY' const SELECT_EMPTY_VALUE_KEY = 'RC_SELECT_EMPTY_VALUE_KEY'
function noop () {} const noop = () => null
function chaining (...fns) { function chaining (...fns) {
return function (...args) { // eslint-disable-line return function (...args) { // eslint-disable-line
// eslint-disable-line // eslint-disable-line
for (let i = 0; i < fns.length; i++) { for (let i = 0; i < fns.length; i++) {
if (fns[i] && typeof fns[i] === 'function') { if (fns[i] && typeof fns[i] === 'function') {
fns[i].apply(this, args) fns[i].apply(chaining, args)
} }
} }
} }
} }
const Select = { const Select = {
inheritAttrs: false, inheritAttrs: false,
Option,
OptGroup,
name: 'Select', name: 'Select',
mixins: [BaseMixin], mixins: [BaseMixin],
props: { props: {
@ -80,6 +82,8 @@ const Select = {
combobox: PropTypes.bool.def(false), combobox: PropTypes.bool.def(false),
tokenSeparators: PropTypes.arrayOf(PropTypes.string).def([]), tokenSeparators: PropTypes.arrayOf(PropTypes.string).def([]),
autoClearSearchValue: PropTypes.bool.def(true), autoClearSearchValue: PropTypes.bool.def(true),
tabIndex: PropTypes.any.def(0),
dropdownRender: PropTypes.func.def(menu => menu),
// onChange: noop, // onChange: noop,
// onFocus: noop, // onFocus: noop,
// onBlur: noop, // onBlur: noop,
@ -99,6 +103,10 @@ const Select = {
this.saveSelectTriggerRef = saveRef(this, 'selectTriggerRef') this.saveSelectTriggerRef = saveRef(this, 'selectTriggerRef')
this.saveRootRef = saveRef(this, 'rootRef') this.saveRootRef = saveRef(this, 'rootRef')
this.saveSelectionRef = saveRef(this, 'selectionRef') this.saveSelectionRef = saveRef(this, 'selectionRef')
this.ariaId = generateUUID()
this._focused = false
this._mouseDown = false
this._options = []
}, },
data () { data () {
const props = getOptionProps(this) const props = getOptionProps(this)
@ -116,6 +124,7 @@ const Select = {
) : '', ) : '',
_open: props.defaultOpen, _open: props.defaultOpen,
_optionsInfo: optionsInfo, _optionsInfo: optionsInfo,
_backfillValue: '',
// a flag for aviod redundant getOptionsInfoFromProps call // a flag for aviod redundant getOptionsInfoFromProps call
_skipBuildOptionsInfo: true, _skipBuildOptionsInfo: true,
} }
@ -140,10 +149,10 @@ const Select = {
if (isMultipleOrTags(this.$props)) { if (isMultipleOrTags(this.$props)) {
const inputNode = this.getInputDOMNode() const inputNode = this.getInputDOMNode()
const mirrorNode = this.getInputMirrorDOMNode() const mirrorNode = this.getInputMirrorDOMNode()
if (inputNode.value) { if (inputNode.value && inputNode.value && mirrorNode) {
inputNode.style.width = '' inputNode.style.width = ''
inputNode.style.width = `${mirrorNode.clientWidth + 10}px` inputNode.style.width = `${mirrorNode.clientWidth + 10}px`
} else { } else if (inputNode) {
inputNode.style.width = '' inputNode.style.width = ''
} }
} }
@ -185,6 +194,91 @@ const Select = {
} }
return newState 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) { onInputChange (event) {
const { tokenSeparators } = this.$props const { tokenSeparators } = this.$props
@ -290,7 +384,7 @@ const Select = {
return return
} }
if (this.getRealOpenState(state)) { if (this.getRealOpenState(state) && this.selectTriggerRef) {
const menu = this.selectTriggerRef.getInnerMenu() const menu = this.selectTriggerRef.getInnerMenu()
if (menu && menu.onKeyDown(event, this.handleBackfill)) { if (menu && menu.onKeyDown(event, this.handleBackfill)) {
event.preventDefault() event.preventDefault()
@ -324,12 +418,8 @@ const Select = {
this.setOpenState(false, true) this.setOpenState(false, true)
} }
this.fireChange(value) this.fireChange(value)
let inputValue const inputValue = isCombobox(props) ? getPropValue(item, props.optionLabelProp) : ''
if (isCombobox(props)) {
inputValue = getPropValue(item, props.optionLabelProp)
} else {
inputValue = ''
}
if (props.autoClearSearchValue) { if (props.autoClearSearchValue) {
this.setInputValue(inputValue, false) this.setInputValue(inputValue, false)
} }
@ -357,7 +447,7 @@ const Select = {
}, },
onPlaceholderClick (e) { onPlaceholderClick (e) {
if (this.getInputDOMNode()) { if (this.getInputDOMNode() && this.getInputDOMNode()) {
this.getInputDOMNode().focus() this.getInputDOMNode().focus()
} }
}, },
@ -389,89 +479,6 @@ const Select = {
onChoiceAnimationLeave () { onChoiceAnimationLeave () {
this.forcePopupAlign() 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) { getOptionInfoBySingleValue (value, optionsInfo) {
let info let info
@ -515,7 +522,8 @@ const Select = {
let value = null let value = null
Object.keys(this.$data._optionsInfo).forEach(key => { Object.keys(this.$data._optionsInfo).forEach(key => {
const info = this.$data._optionsInfo[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 value = info.value
} }
}) })
@ -532,8 +540,8 @@ const Select = {
return value return value
}, },
getVLForOnChange (vls_) { getVLForOnChange (vlsS) {
let vls = vls_ let vls = vlsS
if (vls !== undefined) { if (vls !== undefined) {
if (!this.labelInValue) { if (!this.labelInValue) {
vls = vls.map(v => v) vls = vls.map(v => v)
@ -567,10 +575,11 @@ const Select = {
if (state._inputValue) { if (state._inputValue) {
hidden = true hidden = true
} }
if (state._value.length) { const value = state._value
if (value.length) {
hidden = true 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 hidden = false
} }
const placeholder = props.placeholder const placeholder = props.placeholder
@ -634,10 +643,16 @@ const Select = {
this.setInputValue('') this.setInputValue('')
} else { } else {
// why not use setState? // 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) const tmpValue = this.getValueByInput(inputValue)
if (value !== undefined) { if (tmpValue !== undefined) {
value = tmpValue
this.fireChange(value) this.fireChange(value)
} }
} }
@ -677,9 +692,11 @@ const Select = {
const props = this.$props const props = this.$props
const { _inputValue: inputValue } = this.$data const { _inputValue: inputValue } = this.$data
const attrs = getAttrs(this) const attrs = getAttrs(this)
const defaultInput = <input id={attrs.id} autoComplete='off' />
const inputElement = props.getInputElement const inputElement = props.getInputElement
? props.getInputElement() ? props.getInputElement()
: <input id={attrs.id} autoComplete='off'/> : defaultInput
const inputCls = classnames(getClass(inputElement), { const inputCls = classnames(getClass(inputElement), {
[`${props.prefixCls}-search__field`]: true, [`${props.prefixCls}-search__field`]: true,
}) })
@ -749,34 +766,38 @@ const Select = {
}, },
getPopupDOMNode () { getPopupDOMNode () {
return this.selectTriggerRef.getPopupDOMNode() if (this.selectTriggerRef) {
return this.selectTriggerRef.getPopupDOMNode()
}
}, },
getPopupMenuComponent () { getPopupMenuComponent () {
return this.selectTriggerRef.getInnerMenu() if (this.selectTriggerRef) {
return this.selectTriggerRef.getInnerMenu()
}
}, },
setOpenState (open, needFocus) { setOpenState (open, needFocus) {
const { $props: props, $data: state } = this const { $props: props, $data: state } = this
if (state._open === open) { if (state._open === open) {
this.maybeFocus(open, needFocus) this.maybeFocus(open, !!needFocus)
return return
} }
this.__emit('dropdownVisibleChange', open) this.__emit('dropdownVisibleChange', open)
const nextState = { const nextState = {
_open: open, _open: open,
_backfillValue: undefined, _backfillValue: '',
} }
// clear search input value when open is false in singleMode. // clear search input value when open is false in singleMode.
if (!open && isSingleMode(props) && props.showSearch) { if (!open && isSingleMode(props) && props.showSearch) {
this.setInputValue('', false) this.setInputValue('', false)
} }
if (!open) { if (!open) {
this.maybeFocus(open, needFocus) this.maybeFocus(open, !!needFocus)
} }
this.setState(nextState, () => { this.setState(nextState, () => {
if (open) { 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 const { multiple, tokenSeparators } = this.$props
let nextValue = this.$data._value let nextValue = this.$data._value
let hasNewValue = false let hasNewValue = false
splitBySeparators(string, tokenSeparators).forEach(label => { splitBySeparators(str, tokenSeparators).forEach(label => {
const selectedValue = [label] const selectedValue = [label]
if (multiple) { if (multiple) {
const value = this.getValueByLabel(label) const value = this.getValueByLabel(label)
@ -804,13 +825,10 @@ const Select = {
hasNewValue = true hasNewValue = true
this.fireSelect(value) this.fireSelect(value)
} }
} else { } else if (findIndexInValueBySingleValue(nextValue, label) === -1) {
// tag nextValue = nextValue.concat(selectedValue)
if (findIndexInValueBySingleValue(nextValue, label) === -1) { hasNewValue = true
nextValue = nextValue.concat(selectedValue) this.fireSelect(label)
hasNewValue = true
this.fireSelect(label)
}
} }
}) })
return hasNewValue ? nextValue : undefined return hasNewValue ? nextValue : undefined
@ -833,17 +851,17 @@ const Select = {
}, },
focus () { focus () {
if (isSingleMode(this.$props)) { if (isSingleMode(this.$props) && this.selectionRef) {
this.selectionRef.focus() this.selectionRef.focus()
} else { } else if (this.getInputDOMNode()) {
this.getInputDOMNode().focus() this.getInputDOMNode().focus()
} }
}, },
blur () { blur () {
if (isSingleMode(this.$props)) { if (isSingleMode(this.$props) && this.selectionRef) {
this.selectionRef.blur() this.selectionRef.blur()
} else { } else if (this.getInputDOMNode()) {
this.getInputDOMNode().blur() this.getInputDOMNode().blur()
} }
}, },
@ -880,11 +898,11 @@ const Select = {
} }
let filterFn = this.$props.filterOption let filterFn = this.$props.filterOption
if (hasProp(this, 'filterOption')) { if (hasProp(this, 'filterOption')) {
if (this.filterOption === true) { if (filterFn === true) {
filterFn = defaultFilter filterFn = defaultFilter.bind(this)
} }
} else { } else {
filterFn = defaultFilter filterFn = defaultFilter.bind(this)
} }
if (!filterFn) { if (!filterFn) {
return true return true
@ -900,7 +918,7 @@ const Select = {
if (this.focusTimer) { if (this.focusTimer) {
this.clearFocusTime() this.clearFocusTime()
} }
this.focusTimer = setTimeout(() => { this.focusTimer = window.setTimeout(() => {
// this._focused = true // this._focused = true
// this.updateFocusClassName() // this.updateFocusClassName()
this.$emit('focus') this.$emit('focus')
@ -940,7 +958,7 @@ const Select = {
input.focus() input.focus()
this._focused = true this._focused = true
} }
} else if (activeElement !== this.selectionRef) { } else if (activeElement !== this.selectionRef && this.selectionRef) {
this.selectionRef.focus() this.selectionRef.focus()
this._focused = true this._focused = true
} }
@ -956,8 +974,8 @@ const Select = {
if (e && e.stopPropagation) { if (e && e.stopPropagation) {
e.stopPropagation() e.stopPropagation()
} }
const oldValue = this.$data._value
const value = this.$data._value.filter(singleValue => { const value = oldValue.filter(singleValue => {
return singleValue !== selectedKey return singleValue !== selectedKey
}) })
const canMultiple = isMultipleOrTags(props) const canMultiple = isMultipleOrTags(props)
@ -1006,7 +1024,9 @@ const Select = {
if (!this.$data._open) { if (!this.$data._open) {
return return
} }
this.selectTriggerRef && this.selectTriggerRef.triggerRef.forcePopupAlign() if (this.selectTriggerRef && this.selectTriggerRef.triggerRef) {
this.selectTriggerRef.triggerRef.forcePopupAlign()
}
}, },
renderFilterOptions () { renderFilterOptions () {
const { _inputValue: inputValue } = this.$data const { _inputValue: inputValue } = this.$data
@ -1024,8 +1044,7 @@ const Select = {
value = value.filter(singleValue => { value = value.filter(singleValue => {
return ( return (
childrenKeys.indexOf(singleValue) === -1 && childrenKeys.indexOf(singleValue) === -1 &&
(!inputValue || (!inputValue || String(singleValue).indexOf(String(inputValue)) > -1)
String(singleValue).indexOf(String(inputValue)) > -1)
) )
}) })
value.forEach(singleValue => { value.forEach(singleValue => {
@ -1109,25 +1128,49 @@ const Select = {
return return
} }
if (getSlotOptions(child).isSelectOptGroup) { if (getSlotOptions(child).isSelectOptGroup) {
const innerItems = this.renderFilterOptionsFromChildren( let label = getComponentFromProp(child, 'label')
child.componentOptions.children, let key = child.key
childrenKeys, if (!key && typeof label === 'string') {
menuItems, key = label
) } else if (!label && key) {
if (innerItems.length) { label = key
let label = getComponentFromProp(child, 'label') }
let key = child.key const childChildren = getSlots(child).default
if (!key && typeof label === 'string') { // Match option group label
key = label if (inputValue && this._filterOption(inputValue, child)) {
} else if (!label && key) { const innerItems = childChildren.map(
label = key (subChild) => {
} const childValueSub = getValuePropValue(subChild) || subChild.key
return (
<MenuItem key={childValueSub} value={childValueSub} {...subChild.data}>
{subChild.componentOptions.children}
</MenuItem>
)
},
)
sel.push( sel.push(
<MenuItemGroup key={key} title={label} class ={getClass(child)}> <MenuItemGroup key={key} title={label} class ={getClass(child)}>
{innerItems} {innerItems}
</MenuItemGroup> </MenuItemGroup>,
) )
// Not match
} else {
const innerItems = this.renderFilterOptionsFromChildren(
childChildren,
childrenKeys,
menuItems,
)
if (innerItems.length) {
sel.push(
<MenuItemGroup key={key} title={label} {...child.data}>
{innerItems}
</MenuItemGroup>
)
}
} }
return return
} }
warning( warning(
@ -1242,11 +1285,14 @@ const Select = {
let content = `+ ${value.length - maxTagCount} ...` let content = `+ ${value.length - maxTagCount} ...`
if (maxTagPlaceholder) { if (maxTagPlaceholder) {
content = typeof maxTagPlaceholder === 'function' content = typeof maxTagPlaceholder === 'function'
? maxTagPlaceholder(omittedValues) : maxTagPlaceholder ? maxTagPlaceholder(omittedValues)
: maxTagPlaceholder
} }
maxTagPlaceholderEl = (<li maxTagPlaceholderEl = (<li
style={UNSELECTABLE_STYLE} style={UNSELECTABLE_STYLE}
{...{ attrs: UNSELECTABLE_ATTRIBUTE }}
unselectable='unselectable' unselectable='unselectable'
role='presentation'
onMousedown={preventDefaultEvent} onMousedown={preventDefaultEvent}
class={`${prefixCls}-selection__choice ${prefixCls}-selection__choice__disabled`} class={`${prefixCls}-selection__choice ${prefixCls}-selection__choice__disabled`}
key='maxTagPlaceholder' key='maxTagPlaceholder'
@ -1274,9 +1320,11 @@ const Select = {
return ( return (
<li <li
style={UNSELECTABLE_STYLE} style={UNSELECTABLE_STYLE}
{...{ attrs: UNSELECTABLE_ATTRIBUTE }}
unselectable='unselectable' unselectable='unselectable'
onMousedown={preventDefaultEvent} onMousedown={preventDefaultEvent}
class={choiceClassName} class={choiceClassName}
role='presentation'
key={singleValue || SELECT_EMPTY_VALUE_KEY} key={singleValue || SELECT_EMPTY_VALUE_KEY}
title={toTitle(title)} title={toTitle(title)}
> >
@ -1321,11 +1369,7 @@ const Select = {
</transition-group> </transition-group>
) )
} else { } else {
innerNode = ( innerNode = <ul>{selectedValueNodes}</ul>
<ul>
{selectedValueNodes}
</ul>
)
} }
} }
return ( return (
@ -1342,6 +1386,33 @@ const Select = {
</div> </div>
) )
}, },
renderArrow (multiple) {
const { showArrow, loading, prefixCls } = this.$props
const inputIcon = getComponentFromProp(this, 'inputIcon')
if (!showArrow) {
return null
}
// if loading have loading icon
if (multiple && !loading) {
return null
}
const defaultIcon = loading ? (
<i class={`${prefixCls}-arrow-loading`} />
) : (
<i class={`${prefixCls}-arrow-icon`} />
)
return (
<span
key='arrow'
class={`${prefixCls}-arrow`}
style={UNSELECTABLE_STYLE}
{...{ attrs: UNSELECTABLE_ATTRIBUTE }}
onClick={this.onArrowClick}
>
{inputIcon || defaultIcon}
</span>
)
},
topCtrlContainerClick (e) { topCtrlContainerClick (e) {
if (this.$data._open && !isSingleMode(this.$props)) { if (this.$data._open && !isSingleMode(this.$props)) {
e.stopPropagation() e.stopPropagation()
@ -1412,7 +1483,6 @@ const Select = {
const multiple = isMultipleOrTags(props) const multiple = isMultipleOrTags(props)
const state = this.$data const state = this.$data
const { disabled, prefixCls } = props const { disabled, prefixCls } = props
const inputIcon = getComponentFromProp(this, 'inputIcon')
const ctrlNode = this.renderTopControlNode() const ctrlNode = this.renderTopControlNode()
const { _open: open, _inputValue: inputValue, _value: value } = this.$data const { _open: open, _inputValue: inputValue, _value: value } = this.$data
if (open) { if (open) {
@ -1429,6 +1499,7 @@ const Select = {
'aria-autocomplete': 'list', 'aria-autocomplete': 'list',
'aria-haspopup': 'true', 'aria-haspopup': 'true',
'aria-expanded': realOpen, 'aria-expanded': realOpen,
'aria-controls': this.ariaId,
}, },
on: { on: {
click: this.selectionRefClick, click: this.selectionRefClick,
@ -1445,7 +1516,7 @@ const Select = {
selectionProps.on.keydown = this.onKeyDown selectionProps.on.keydown = this.onKeyDown
selectionProps.on.focus = this.selectionRefFocus selectionProps.on.focus = this.selectionRefFocus
selectionProps.on.blur = this.selectionRefBlur selectionProps.on.blur = this.selectionRefBlur
selectionProps.attrs.tabIndex = props.disabled ? -1 : 0 selectionProps.attrs.tabIndex = props.disabled ? -1 : props.tabIndex
} }
const rootCls = { const rootCls = {
[prefixCls]: true, [prefixCls]: true,
@ -1492,6 +1563,8 @@ const Select = {
name: 'ant-ref', name: 'ant-ref',
value: this.saveSelectTriggerRef, value: this.saveSelectTriggerRef,
}] }} }] }}
dropdownRender={props.dropdownRender}
ariaId={this.ariaId}
> >
<div <div
{...{ directives: [{ {...{ directives: [{
@ -1510,16 +1583,7 @@ const Select = {
<div {...selectionProps}> <div {...selectionProps}>
{ctrlNode} {ctrlNode}
{this.renderClear()} {this.renderClear()}
{multiple || !props.showArrow ? null : ( {this.renderArrow(!!multiple)}
<span
key='arrow'
class={`${prefixCls}-arrow`}
style={UNSELECTABLE_STYLE}
unselectable='unselectable'
// onClick={this.onArrowClick}
>
{inputIcon || <i class={`${prefixCls}-arrow-icon`} />}
</span>)}
</div> </div>
</div> </div>
</SelectTrigger> </SelectTrigger>

View File

@ -55,6 +55,8 @@ export default {
getPopupContainer: PropTypes.func, getPopupContainer: PropTypes.func,
backfillValue: PropTypes.any, backfillValue: PropTypes.any,
menuItemSelectedIcon: PropTypes.any, menuItemSelectedIcon: PropTypes.any,
dropdownRender: PropTypes.func,
ariaId: PropTypes.string,
}, },
created () { created () {
this.saveDropdownMenuRef = saveRef(this, 'dropdownMenuRef') this.saveDropdownMenuRef = saveRef(this, 'dropdownMenuRef')
@ -62,7 +64,7 @@ export default {
}, },
data () { data () {
return { return {
dropdownWidth: null, dropdownWidth: 0,
} }
}, },
@ -99,9 +101,13 @@ export default {
dropdownMenuStyle, getDropdownPrefixCls, backfillValue, menuItemSelectedIcon, dropdownMenuStyle, getDropdownPrefixCls, backfillValue, menuItemSelectedIcon,
} = this } = this
const { menuSelect, menuDeselect, popupScroll } = this.$listeners const { menuSelect, menuDeselect, popupScroll } = this.$listeners
const props = this.$props
const { dropdownRender, ariaId } = props
const dropdownMenuProps = { const dropdownMenuProps = {
props: { props: {
...newProps.props, ...newProps.props,
ariaId,
prefixCls: getDropdownPrefixCls(), prefixCls: getDropdownPrefixCls(),
value, firstActiveValue, defaultActiveFirstOption, dropdownMenuStyle, value, firstActiveValue, defaultActiveFirstOption, dropdownMenuStyle,
backfillValue, backfillValue,
@ -118,9 +124,12 @@ export default {
value: this.saveDropdownMenuRef, value: this.saveDropdownMenuRef,
}], }],
} }
return ( const menuNode = <DropdownMenu {...dropdownMenuProps} />
<DropdownMenu {...dropdownMenuProps} />
) if (dropdownRender) {
return dropdownRender(menuNode, props)
}
return null
}, },
getDropdownTransitionName () { getDropdownTransitionName () {

View File

@ -1,7 +1,16 @@
@selectPrefixCls: rc-select; @selectPrefixCls: rc-select;
@keyframes select-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.effect() { .effect() {
animation-duration: .3s; animation-duration: 0.3s;
animation-fill-mode: both; animation-fill-mode: both;
transform-origin: 0 0; transform-origin: 0 0;
} }
@ -14,7 +23,8 @@
color: #666; color: #666;
line-height: 28px; line-height: 28px;
ul, li { ul,
li {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
@ -35,17 +45,36 @@
outline: none; outline: none;
} }
&-arrow &-arrow-loading {
display: inline-block;
width: 18px;
height: 18px;
margin-top: 6px;
margin-left: -4px;
&:after {
content: ' ';
display: block;
width: 12px;
height: 12px;
margin: 2px;
border-radius: 50%;
border: 2px solid #999999;
border-color: #999999 transparent #999999 transparent;
animation: select-ring 1.2s linear infinite;
}
}
&-arrow &-arrow-icon { &-arrow &-arrow-icon {
border-color: #999999 transparent transparent transparent; border-color: #999999 transparent transparent transparent;
border-style: solid; border-style: solid;
border-width: 5px 4px 0 4px; border-width: 5px 4px 0 4px;
height: 0; height: 0;
width: 0; width: 0;
margin-left: -4px; margin-left: -4px;
margin-top: -2px; margin-top: -2px;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
} }
&-selection { &-selection {
@ -251,15 +280,18 @@
margin-right: 4px; margin-right: 4px;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
transition: padding .3s cubic-bezier(0.6, -0.28, 0.735, 0.045), width .3s cubic-bezier(0.6, -0.28, 0.735, 0.045); transition: padding 0.3s cubic-bezier(0.6, -0.28, 0.735, 0.045),
width 0.3s cubic-bezier(0.6, -0.28, 0.735, 0.045);
&__content { &__content {
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
transition: margin .3s cubic-bezier(0.165, 0.84, 0.44, 1); transition: margin 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
} }
&-zoom-enter, &-zoom-appear, &-zoom-leave { &-zoom-enter,
&-zoom-appear,
&-zoom-leave {
.effect(); .effect();
opacity: 0; opacity: 0;
animation-play-state: paused; animation-play-state: paused;
@ -310,7 +342,7 @@
transform: scale(0); transform: scale(0);
top: 0; top: 0;
right: 2px; right: 2px;
transition: opacity .3s, transform .3s; transition: opacity 0.3s, transform 0.3s;
&-icon { &-icon {
font-style: normal; font-style: normal;
@ -402,7 +434,8 @@
} }
} }
&-slide-up-enter, &-slide-up-appear { &-slide-up-enter,
&-slide-up-appear {
.effect(); .effect();
opacity: 0; opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
@ -416,7 +449,8 @@
animation-play-state: paused; animation-play-state: paused;
} }
&-slide-up-enter&-slide-up-enter-active&-placement-bottomLeft, &-slide-up-appear&-slide-up-appear-active&-placement-bottomLeft { &-slide-up-enter&-slide-up-enter-active&-placement-bottomLeft,
&-slide-up-appear&-slide-up-appear-active&-placement-bottomLeft {
animation-name: rcSelectDropdownSlideUpIn; animation-name: rcSelectDropdownSlideUpIn;
animation-play-state: running; animation-play-state: running;
} }
@ -426,7 +460,8 @@
animation-play-state: running; animation-play-state: running;
} }
&-slide-up-enter&-slide-up-enter-active&-placement-topLeft, &-slide-up-appear&-slide-up-appear-active&-placement-topLeft { &-slide-up-enter&-slide-up-enter-active&-placement-topLeft,
&-slide-up-appear&-slide-up-appear-active&-placement-topLeft {
animation-name: rcSelectDropdownSlideDownIn; animation-name: rcSelectDropdownSlideDownIn;
animation-play-state: running; animation-play-state: running;
} }
@ -493,5 +528,4 @@
border-width: 0 4px 5px 4px; border-width: 0 4px 5px 4px;
} }
} }
} }

View File

@ -5,7 +5,7 @@ export function toTitle (title) {
if (typeof title === 'string') { if (typeof title === 'string') {
return title return title
} }
return null return ''
} }
export function getValuePropValue (child) { export function getValuePropValue (child) {
if (!child) { if (!child) {
@ -24,9 +24,7 @@ export function getValuePropValue (child) {
return label return label
} }
} }
throw new Error( throw new Error(`Need at least a key or a value or a label (only for OptGroup) for ${child}`)
`Need at least a key or a value or a label(slot) (only for OptGroup) for ${child}`
)
} }
export function getPropValue (child, prop) { export function getPropValue (child, prop) {
@ -88,10 +86,12 @@ export function preventDefaultEvent (e) {
export function findIndexInValueBySingleValue (value, singleValue) { export function findIndexInValueBySingleValue (value, singleValue) {
let index = -1 let index = -1
for (let i = 0; i < value.length; i++) { if (value) {
if (value[i] === singleValue) { for (let i = 0; i < value.length; i++) {
index = i if (value[i] === singleValue) {
break index = i
break
}
} }
} }
return index return index
@ -100,10 +100,12 @@ export function findIndexInValueBySingleValue (value, singleValue) {
export function getLabelFromPropsValue (value, key) { export function getLabelFromPropsValue (value, key) {
let label let label
value = toArray(value) value = toArray(value)
for (let i = 0; i < value.length; i++) { if (value) {
if (value[i].key === key) { for (let i = 0; i < value.length; i++) {
label = value[i].label if (value[i].key === key) {
break label = value[i].label
break
}
} }
} }
return label return label
@ -155,18 +157,18 @@ export function findFirstMenuItem (children) {
return null return null
} }
export function includesSeparators (string, separators) { export function includesSeparators (str, separators) {
for (let i = 0; i < separators.length; ++i) { for (let i = 0; i < separators.length; ++i) {
if (string.lastIndexOf(separators[i]) > 0) { if (str.lastIndexOf(separators[i]) > 0) {
return true return true
} }
} }
return false return false
} }
export function splitBySeparators (string, separators) { export function splitBySeparators (str, separators) {
const reg = new RegExp(`[${separators.join()}]`) const reg = new RegExp(`[${separators.join()}]`)
return string.split(reg).filter(token => token) return str.split(reg).filter(token => token)
} }
export function defaultFilterFn (input, child) { export function defaultFilterFn (input, child) {
@ -180,9 +182,7 @@ export function defaultFilterFn (input, child) {
} else { } else {
value = String(value) value = String(value)
} }
return ( return value.toLowerCase().indexOf(input.toLowerCase()) > -1
value.toLowerCase().indexOf(input.toLowerCase()) > -1
)
} }
export function validateOptionValue (value, props) { export function validateOptionValue (value, props) {
@ -192,7 +192,7 @@ export function validateOptionValue (value, props) {
if (typeof value !== 'string') { if (typeof value !== 'string') {
throw new Error( throw new Error(
`Invalid \`value\` of type \`${typeof value}\` supplied to Option, ` + `Invalid \`value\` of type \`${typeof value}\` supplied to Option, ` +
`expected \`string\` when \`tags/combobox\` is \`true\`.` `expected \`string\` when \`tags/combobox\` is \`true\`.`,
) )
} }
} }
@ -202,3 +202,16 @@ export function saveRef (instance, name) {
instance[name] = node instance[name] = node
} }
} }
export function generateUUID () {
if (process.env.NODE_ENV === 'test') {
return 'test-uuid'
}
let d = new Date().getTime()
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = (d + Math.random() * 16) % 16 | 0
d = Math.floor(d / 16)
return (c === 'x' ? r : (r & 0x7) | 0x8).toString(16)
})
return uuid
}