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 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 => {

View File

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

View File

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

View File

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

View File

@ -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]),
}

View File

@ -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 = <input id={attrs.id} autoComplete='off' />
const inputElement = props.getInputElement
? props.getInputElement()
: <input id={attrs.id} autoComplete='off'/>
: 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 (
<MenuItem key={childValueSub} value={childValueSub} {...subChild.data}>
{subChild.componentOptions.children}
</MenuItem>
)
},
)
sel.push(
<MenuItemGroup key={key} title={label} class ={getClass(child)}>
{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
}
warning(
@ -1242,11 +1285,14 @@ const Select = {
let content = `+ ${value.length - maxTagCount} ...`
if (maxTagPlaceholder) {
content = typeof maxTagPlaceholder === 'function'
? maxTagPlaceholder(omittedValues) : maxTagPlaceholder
? maxTagPlaceholder(omittedValues)
: maxTagPlaceholder
}
maxTagPlaceholderEl = (<li
style={UNSELECTABLE_STYLE}
{...{ attrs: UNSELECTABLE_ATTRIBUTE }}
unselectable='unselectable'
role='presentation'
onMousedown={preventDefaultEvent}
class={`${prefixCls}-selection__choice ${prefixCls}-selection__choice__disabled`}
key='maxTagPlaceholder'
@ -1274,9 +1320,11 @@ const Select = {
return (
<li
style={UNSELECTABLE_STYLE}
{...{ attrs: UNSELECTABLE_ATTRIBUTE }}
unselectable='unselectable'
onMousedown={preventDefaultEvent}
class={choiceClassName}
role='presentation'
key={singleValue || SELECT_EMPTY_VALUE_KEY}
title={toTitle(title)}
>
@ -1321,11 +1369,7 @@ const Select = {
</transition-group>
)
} else {
innerNode = (
<ul>
{selectedValueNodes}
</ul>
)
innerNode = <ul>{selectedValueNodes}</ul>
}
}
return (
@ -1342,6 +1386,33 @@ const Select = {
</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) {
if (this.$data._open && !isSingleMode(this.$props)) {
e.stopPropagation()
@ -1412,7 +1483,6 @@ const Select = {
const multiple = isMultipleOrTags(props)
const state = this.$data
const { disabled, prefixCls } = props
const inputIcon = getComponentFromProp(this, 'inputIcon')
const ctrlNode = this.renderTopControlNode()
const { _open: open, _inputValue: inputValue, _value: value } = this.$data
if (open) {
@ -1429,6 +1499,7 @@ const Select = {
'aria-autocomplete': 'list',
'aria-haspopup': 'true',
'aria-expanded': realOpen,
'aria-controls': this.ariaId,
},
on: {
click: this.selectionRefClick,
@ -1445,7 +1516,7 @@ const Select = {
selectionProps.on.keydown = this.onKeyDown
selectionProps.on.focus = this.selectionRefFocus
selectionProps.on.blur = this.selectionRefBlur
selectionProps.attrs.tabIndex = props.disabled ? -1 : 0
selectionProps.attrs.tabIndex = props.disabled ? -1 : props.tabIndex
}
const rootCls = {
[prefixCls]: true,
@ -1492,6 +1563,8 @@ const Select = {
name: 'ant-ref',
value: this.saveSelectTriggerRef,
}] }}
dropdownRender={props.dropdownRender}
ariaId={this.ariaId}
>
<div
{...{ directives: [{
@ -1510,16 +1583,7 @@ const Select = {
<div {...selectionProps}>
{ctrlNode}
{this.renderClear()}
{multiple || !props.showArrow ? null : (
<span
key='arrow'
class={`${prefixCls}-arrow`}
style={UNSELECTABLE_STYLE}
unselectable='unselectable'
// onClick={this.onArrowClick}
>
{inputIcon || <i class={`${prefixCls}-arrow-icon`} />}
</span>)}
{this.renderArrow(!!multiple)}
</div>
</div>
</SelectTrigger>

View File

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

View File

@ -1,7 +1,16 @@
@selectPrefixCls: rc-select;
@keyframes select-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.effect() {
animation-duration: .3s;
animation-duration: 0.3s;
animation-fill-mode: both;
transform-origin: 0 0;
}
@ -14,7 +23,8 @@
color: #666;
line-height: 28px;
ul, li {
ul,
li {
margin: 0;
padding: 0;
list-style: none;
@ -35,17 +45,36 @@
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 {
border-color: #999999 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
width: 0;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
left: 50%;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
width: 0;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
left: 50%;
}
&-selection {
@ -251,15 +280,18 @@
margin-right: 4px;
position: relative;
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 {
margin-left: 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();
opacity: 0;
animation-play-state: paused;
@ -310,7 +342,7 @@
transform: scale(0);
top: 0;
right: 2px;
transition: opacity .3s, transform .3s;
transition: opacity 0.3s, transform 0.3s;
&-icon {
font-style: normal;
@ -402,7 +434,8 @@
}
}
&-slide-up-enter, &-slide-up-appear {
&-slide-up-enter,
&-slide-up-appear {
.effect();
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
@ -416,7 +449,8 @@
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-play-state: running;
}
@ -426,7 +460,8 @@
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-play-state: running;
}
@ -493,5 +528,4 @@
border-width: 0 4px 5px 4px;
}
}
}

View File

@ -5,7 +5,7 @@ export function toTitle (title) {
if (typeof title === 'string') {
return title
}
return null
return ''
}
export function getValuePropValue (child) {
if (!child) {
@ -24,9 +24,7 @@ export function getValuePropValue (child) {
return label
}
}
throw new Error(
`Need at least a key or a value or a label(slot) (only for OptGroup) for ${child}`
)
throw new Error(`Need at least a key or a value or a label (only for OptGroup) for ${child}`)
}
export function getPropValue (child, prop) {
@ -88,10 +86,12 @@ export function preventDefaultEvent (e) {
export function findIndexInValueBySingleValue (value, singleValue) {
let index = -1
for (let i = 0; i < value.length; i++) {
if (value[i] === singleValue) {
index = i
break
if (value) {
for (let i = 0; i < value.length; i++) {
if (value[i] === singleValue) {
index = i
break
}
}
}
return index
@ -100,10 +100,12 @@ export function findIndexInValueBySingleValue (value, singleValue) {
export function getLabelFromPropsValue (value, key) {
let label
value = toArray(value)
for (let i = 0; i < value.length; i++) {
if (value[i].key === key) {
label = value[i].label
break
if (value) {
for (let i = 0; i < value.length; i++) {
if (value[i].key === key) {
label = value[i].label
break
}
}
}
return label
@ -155,18 +157,18 @@ export function findFirstMenuItem (children) {
return null
}
export function includesSeparators (string, separators) {
export function includesSeparators (str, separators) {
for (let i = 0; i < separators.length; ++i) {
if (string.lastIndexOf(separators[i]) > 0) {
if (str.lastIndexOf(separators[i]) > 0) {
return true
}
}
return false
}
export function splitBySeparators (string, separators) {
export function splitBySeparators (str, separators) {
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) {
@ -180,9 +182,7 @@ export function defaultFilterFn (input, child) {
} else {
value = String(value)
}
return (
value.toLowerCase().indexOf(input.toLowerCase()) > -1
)
return value.toLowerCase().indexOf(input.toLowerCase()) > -1
}
export function validateOptionValue (value, props) {
@ -192,7 +192,7 @@ export function validateOptionValue (value, props) {
if (typeof value !== 'string') {
throw new Error(
`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
}
}
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
}