import KeyCode from '../_util/KeyCode' import PropTypes from '../_util/vue-types' import classnames from 'classnames' import classes from 'component-classes' import { Item as MenuItem, ItemGroup as MenuItemGroup } from '../vc-menu' import warning from 'warning' import Vue from 'vue' import Option from './Option' 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' import SelectTrigger from './SelectTrigger' import { defaultFilterFn, findFirstMenuItem, findIndexInValueBySingleValue, generateUUID, getLabelFromPropsValue, getMapKey, getPropValue, getValuePropValue, includesSeparators, isCombobox, isMultipleOrTags, isMultipleOrTagsOrCombobox, isSingleMode, preventDefaultEvent, saveRef, splitBySeparators, toArray, toTitle, UNSELECTABLE_ATTRIBUTE, UNSELECTABLE_STYLE, validateOptionValue, } from './util' import { SelectPropTypes } from './PropTypes' Vue.use(ref, { name: 'ant-ref' }) const SELECT_EMPTY_VALUE_KEY = 'RC_SELECT_EMPTY_VALUE_KEY' 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(chaining, args) } } } } const Select = { inheritAttrs: false, Option, OptGroup, name: 'Select', mixins: [BaseMixin], props: { ...SelectPropTypes, prefixCls: SelectPropTypes.prefixCls.def('rc-select'), defaultOpen: PropTypes.bool.def(false), labelInValue: SelectPropTypes.labelInValue.def(false), defaultActiveFirstOption: SelectPropTypes.defaultActiveFirstOption.def(true), showSearch: SelectPropTypes.showSearch.def(true), allowClear: SelectPropTypes.allowClear.def(false), placeholder: SelectPropTypes.placeholder.def(''), showArrow: SelectPropTypes.showArrow.def(true), dropdownMatchSelectWidth: PropTypes.bool.def(true), dropdownStyle: SelectPropTypes.dropdownStyle.def({}), dropdownMenuStyle: PropTypes.object.def({}), optionFilterProp: SelectPropTypes.optionFilterProp.def('value'), optionLabelProp: SelectPropTypes.optionLabelProp.def('value'), notFoundContent: PropTypes.any.def('Not Found'), backfill: PropTypes.bool.def(false), showAction: SelectPropTypes.showAction.def(['click']), 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, // onSelect: noop, // onSearch: noop, // onDeselect: noop, // onInputKeydown: noop, }, model: { prop: 'value', event: 'change', }, created () { this.saveInputRef = saveRef(this, 'inputRef') this.saveInputMirrorRef = saveRef(this, 'inputMirrorRef') this.saveTopCtrlRef = saveRef(this, 'topCtrlRef') 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) const optionsInfo = this.getOptionsInfoFromProps(props) warning( this.__propsSymbol__, 'Replace slots.default with props.children and pass props.__propsSymbol__' ) const state = { _value: this.getValueFromProps(props, true), // true: use default value _inputValue: props.combobox ? this.getInputValueForCombobox( props, optionsInfo, true, // use default value ) : '', _open: props.defaultOpen, _optionsInfo: optionsInfo, _backfillValue: '', // a flag for aviod redundant getOptionsInfoFromProps call _skipBuildOptionsInfo: true, } return { ...state, ...this.getDerivedStateFromProps(props, state), } }, mounted () { this.$nextTick(() => { this.autoFocus && this.focus() }) }, watch: { __propsSymbol__ () { Object.assign(this.$data, this.getDerivedStateFromProps(getOptionProps(this), this.$data)) }, }, updated () { this.$nextTick(() => { if (isMultipleOrTags(this.$props)) { const inputNode = this.getInputDOMNode() const mirrorNode = this.getInputMirrorDOMNode() if (inputNode.value && inputNode.value && mirrorNode) { inputNode.style.width = '' inputNode.style.width = `${mirrorNode.clientWidth + 10}px` } else if (inputNode) { inputNode.style.width = '' } } this.forcePopupAlign() }) }, beforeDestroy () { this.clearFocusTime() this.clearBlurTime() if (this.dropdownContainer) { document.body.removeChild(this.dropdownContainer) this.dropdownContainer = null } }, methods: { getDerivedStateFromProps (nextProps, prevState) { const optionsInfo = prevState._skipBuildOptionsInfo ? prevState._optionsInfo : this.getOptionsInfoFromProps(nextProps, prevState) const newState = { _optionsInfo: optionsInfo, _skipBuildOptionsInfo: false, } if ('open' in nextProps) { newState._open = nextProps.open } if ('value' in nextProps) { const value = this.getValueFromProps(nextProps) newState._value = value if (nextProps.combobox) { newState._inputValue = this.getInputValueForCombobox( nextProps, optionsInfo, ) } } 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 const val = event.target.value if ( isMultipleOrTags(this.$props) && tokenSeparators.length && includesSeparators(val, tokenSeparators) ) { const nextValue = this.getValueByInput(val) if (nextValue !== undefined) { this.fireChange(nextValue) } this.setOpenState(false, true) this.setInputValue('', false) return } this.setInputValue(val) this.setState({ _open: true, }) if (isCombobox(this.$props)) { this.fireChange([val]) } }, onDropdownVisibleChange (open) { if (open && !this._focused) { this.clearBlurTime() this.timeoutFocus() this._focused = true this.updateFocusClassName() } this.setOpenState(open) }, // combobox ignore onKeyDown (event) { const { _open: open } = this.$data const { disabled } = this.$props if (disabled) { return } const keyCode = event.keyCode if (open && !this.getInputDOMNode()) { this.onInputKeydown(event) } else if ( keyCode === KeyCode.ENTER || keyCode === KeyCode.DOWN ) { // vue state是同步更新,onKeyDown在onMenuSelect后会再次调用,单选时不在调用setOpenState if (keyCode === KeyCode.ENTER && !isMultipleOrTags(this.$props)) { this.maybeFocus(true) } else if (!open) { this.setOpenState(true) } event.preventDefault() } else if (keyCode === KeyCode.SPACE) { // Not block space if popup is shown if (!open) { this.setOpenState(true) event.preventDefault() } } }, onInputKeydown (event) { const props = this.$props if (props.disabled) { return } const state = this.$data const keyCode = event.keyCode if ( isMultipleOrTags(props) && !event.target.value && keyCode === KeyCode.BACKSPACE ) { event.preventDefault() const { _value: value } = state if (value.length) { this.removeSelected(value[value.length - 1]) } return } if (keyCode === KeyCode.DOWN) { if (!state._open) { this.openIfHasChildren() event.preventDefault() event.stopPropagation() return } } else if (keyCode === KeyCode.ENTER && state._open) { // Aviod trigger form submit when select item // https://github.com/ant-design/ant-design/issues/10861 event.preventDefault() } else if (keyCode === KeyCode.ESC) { if (state._open) { this.setOpenState(false) event.preventDefault() event.stopPropagation() } return } if (this.getRealOpenState(state) && this.selectTriggerRef) { const menu = this.selectTriggerRef.getInnerMenu() if (menu && menu.onKeyDown(event, this.handleBackfill)) { event.preventDefault() event.stopPropagation() } } }, onMenuSelect ({ item }) { if (!item) { return } let value = this.$data._value const props = this.$props const selectedValue = getValuePropValue(item) const lastValue = value[value.length - 1] this.fireSelect(selectedValue) if (isMultipleOrTags(props)) { if (findIndexInValueBySingleValue(value, selectedValue) !== -1) { return } value = value.concat([selectedValue]) } else { if (lastValue !== undefined && lastValue === selectedValue && selectedValue !== this.$data._backfillValue) { this.setOpenState(false, true) return } value = [selectedValue] this.setOpenState(false, true) } this.fireChange(value) const inputValue = isCombobox(props) ? getPropValue(item, props.optionLabelProp) : '' if (props.autoClearSearchValue) { this.setInputValue(inputValue, false) } }, onMenuDeselect ({ item, domEvent }) { if (domEvent.type === 'keydown' && domEvent.keyCode === KeyCode.ENTER) { this.removeSelected(getValuePropValue(item)) return } if (domEvent.type === 'click') { this.removeSelected(getValuePropValue(item)) } if (this.autoClearSearchValue) { this.setInputValue('', false) } }, onArrowClick (e) { e.stopPropagation() e.preventDefault() if (!this.disabled) { this.setOpenState(!this.$data._open, !this.$data._open) } }, onPlaceholderClick (e) { if (this.getInputDOMNode() && this.getInputDOMNode()) { this.getInputDOMNode().focus() } }, onPopupFocus () { // fix ie scrollbar, focus element again this.maybeFocus(true, true) }, onClearSelection (event) { const props = this.$props const state = this.$data if (props.disabled) { return } const { _inputValue: inputValue, _value: value } = state event.stopPropagation() if (inputValue || value.length) { if (value.length) { this.fireChange([]) } this.setOpenState(false, true) if (inputValue) { this.setInputValue('') } } }, onChoiceAnimationLeave () { this.forcePopupAlign() }, getOptionInfoBySingleValue (value, optionsInfo) { let info optionsInfo = optionsInfo || this.$data._optionsInfo if (optionsInfo[getMapKey(value)]) { info = optionsInfo[getMapKey(value)] } if (info) { return info } let defaultLabel = value if (this.$props.labelInValue) { const label = getLabelFromPropsValue(this.$props.value, value) if (label !== undefined) { defaultLabel = label } } const defaultInfo = { option: , value, label: defaultLabel, } return defaultInfo }, getOptionBySingleValue (value) { const { option } = this.getOptionInfoBySingleValue(value) return option }, getOptionsBySingleValue (values) { return values.map(value => { return this.getOptionBySingleValue(value) }) }, getValueByLabel (label) { if (label === undefined) { return null } let value = null Object.keys(this.$data._optionsInfo).forEach(key => { const info = this.$data._optionsInfo[key] const oldLable = toArray(info.label) if (oldLable && oldLable.join('') === label) { value = info.value } }) return value }, getVLBySingleValue (value) { if (this.$props.labelInValue) { return { key: value, label: this.getLabelBySingleValue(value), } } return value }, getVLForOnChange (vlsS) { let vls = vlsS if (vls !== undefined) { if (!this.labelInValue) { vls = vls.map(v => v) } else { vls = vls.map(vl => ({ key: vl, label: this.getLabelBySingleValue(vl), })) } return isMultipleOrTags(this.$props) ? vls : vls[0] } return vls }, getLabelBySingleValue (value, optionsInfo) { const { label } = this.getOptionInfoBySingleValue(value, optionsInfo) return label }, getDropdownContainer () { if (!this.dropdownContainer) { this.dropdownContainer = document.createElement('div') document.body.appendChild(this.dropdownContainer) } return this.dropdownContainer }, getPlaceholderElement () { const { $props: props, $data: state } = this let hidden = false if (state._inputValue) { hidden = true } const value = state._value if (value.length) { hidden = true } if (isCombobox(props) && value.length === 1 && (state._value && !state._value[0])) { hidden = false } const placeholder = props.placeholder if (placeholder) { const p = { on: { mousedown: preventDefaultEvent, click: this.onPlaceholderClick, }, attrs: UNSELECTABLE_ATTRIBUTE, style: { display: hidden ? 'none' : 'block', ...UNSELECTABLE_STYLE, }, class: `${props.prefixCls}-selection__placeholder`, } return (
{placeholder}
) } return null }, inputClick (e) { if (this.$data._open) { this.clearBlurTime() e.stopPropagation() } else { this._focused = false } }, inputBlur (e) { this.clearBlurTime() if (this.disabled) { return } this.blurTimer = setTimeout(() => { this._focused = false this.updateFocusClassName() const props = this.$props let { _value: value } = this.$data const { _inputValue: inputValue } = this.$data if ( isSingleMode(props) && props.showSearch && inputValue && props.defaultActiveFirstOption ) { const options = this._options || [] if (options.length) { const firstOption = findFirstMenuItem(options) if (firstOption) { value = [getValuePropValue(firstOption)] this.fireChange(value) } } } else if (isMultipleOrTags(props) && inputValue) { if (this._mouseDown) { // need update dropmenu when not blur this.setInputValue('') } else { // why not use setState? this.$data._inputValue = '' this.$nextTick(() => { if (this.getInputDOMNode && this.getInputDOMNode()) { this.getInputDOMNode().value = '' } }) } const tmpValue = this.getValueByInput(inputValue) if (tmpValue !== undefined) { value = tmpValue this.fireChange(value) } } // if click the rest space of Select in multiple mode if (isMultipleOrTags(props) && this._mouseDown) { this.maybeFocus(true, true) this._mouseDown = false return } this.setOpenState(false) this.$emit('blur', this.getVLForOnChange(value)) }, 10) }, inputFocus (e) { if (this.$props.disabled) { e.preventDefault() return } this.clearBlurTime() if ( !isMultipleOrTagsOrCombobox(this.$props) && e.target === this.getInputDOMNode() ) { return } if (this._focused) { return } this._focused = true this.updateFocusClassName() // only effect multiple or tag mode if (!isMultipleOrTags(this.$props) || !this._mouseDown) { this.timeoutFocus() } }, _getInputElement () { 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, }) const inputEvents = getEvents(inputElement) // https://github.com/ant-design/ant-design/issues/4992#issuecomment-281542159 // Add space to the end of the inputValue as the width measurement tolerance inputElement.data = inputElement.data || {} return (
{cloneElement(inputElement, { props: { disabled: props.disabled, value: inputValue, }, attrs: { ...(inputElement.data.attrs || {}), disabled: props.disabled, value: inputValue, }, domProps: { value: inputValue, }, class: inputCls, directives: [{ name: 'ant-ref', value: this.saveInputRef, }], on: { input: this.onInputChange, keydown: chaining( this.onInputKeydown, inputEvents.keydown, this.$listeners.inputKeydown ), focus: chaining( this.inputFocus, inputEvents.focus, ), blur: chaining( this.inputBlur, inputEvents.blur, ), }, })} {inputValue} 
) }, getInputDOMNode () { return this.topCtrlRef ? this.topCtrlRef.querySelector('input,textarea,div[contentEditable]') : this.inputRef }, getInputMirrorDOMNode () { return this.inputMirrorRef }, getPopupDOMNode () { if (this.selectTriggerRef) { return this.selectTriggerRef.getPopupDOMNode() } }, getPopupMenuComponent () { if (this.selectTriggerRef) { return this.selectTriggerRef.getInnerMenu() } }, setOpenState (open, needFocus) { const { $props: props, $data: state } = this if (state._open === open) { this.maybeFocus(open, !!needFocus) return } this.__emit('dropdownVisibleChange', open) const nextState = { _open: open, _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.setState(nextState, () => { if (open) { this.maybeFocus(open, !!needFocus) } }) }, setInputValue (inputValue, fireSearch = true) { if (inputValue !== this.$data._inputValue) { this.setState({ _inputValue: inputValue, }, this.forcePopupAlign) if (fireSearch) { this.$emit('search', inputValue) } } }, getValueByInput (str) { const { multiple, tokenSeparators } = this.$props let nextValue = this.$data._value let hasNewValue = false splitBySeparators(str, tokenSeparators).forEach(label => { const selectedValue = [label] if (multiple) { const value = this.getValueByLabel(label) if (value && findIndexInValueBySingleValue(nextValue, value) === -1) { nextValue = nextValue.concat(value) hasNewValue = true this.fireSelect(value) } } else if (findIndexInValueBySingleValue(nextValue, label) === -1) { nextValue = nextValue.concat(selectedValue) hasNewValue = true this.fireSelect(label) } }) return hasNewValue ? nextValue : undefined }, getRealOpenState (state) { const { open: _open } = this.$props if (typeof _open === 'boolean') { return _open } let open = (state || this.$data)._open const options = this._options || [] if (isMultipleOrTagsOrCombobox(this.$props) || !this.$props.showSearch) { if (open && !options.length) { open = false } } return open }, focus () { if (isSingleMode(this.$props) && this.selectionRef) { this.selectionRef.focus() } else if (this.getInputDOMNode()) { this.getInputDOMNode().focus() } }, blur () { if (isSingleMode(this.$props) && this.selectionRef) { this.selectionRef.blur() } else if (this.getInputDOMNode()) { this.getInputDOMNode().blur() } }, markMouseDown () { this._mouseDown = true }, markMouseLeave () { this._mouseDown = false }, handleBackfill (item) { if (!this.backfill || !(isSingleMode(this.$props) || isCombobox(this.$props))) { return } const key = getValuePropValue(item) if (isCombobox(this.$props)) { this.setInputValue(key, false) } this.setState({ _value: [key], _backfillValue: key, }) }, _filterOption (input, child, defaultFilter = defaultFilterFn) { const { _value: value, _backfillValue: backfillValue } = this.$data const lastValue = value[value.length - 1] if (!input || (lastValue && lastValue === backfillValue)) { return true } let filterFn = this.$props.filterOption if (hasProp(this, 'filterOption')) { if (filterFn === true) { filterFn = defaultFilter.bind(this) } } else { filterFn = defaultFilter.bind(this) } if (!filterFn) { return true } else if (typeof filterFn === 'function') { return filterFn.call(this, input, child) } else if (getValue(child, 'disabled')) { return false } return true }, timeoutFocus () { if (this.focusTimer) { this.clearFocusTime() } this.focusTimer = window.setTimeout(() => { // this._focused = true // this.updateFocusClassName() this.$emit('focus') }, 10) }, clearFocusTime () { if (this.focusTimer) { clearTimeout(this.focusTimer) this.focusTimer = null } }, clearBlurTime () { if (this.blurTimer) { clearTimeout(this.blurTimer) this.blurTimer = null } }, updateFocusClassName () { const { rootRef, prefixCls } = this // avoid setState and its side effect if (this._focused) { classes(rootRef).add(`${prefixCls}-focused`) } else { classes(rootRef).remove(`${prefixCls}-focused`) } }, maybeFocus (open, needFocus) { if (needFocus || open) { const input = this.getInputDOMNode() const { activeElement } = document if (input && (open || isMultipleOrTagsOrCombobox(this.$props))) { if (activeElement !== input) { input.focus() this._focused = true } } else if (activeElement !== this.selectionRef && this.selectionRef) { this.selectionRef.focus() this._focused = true } } }, removeSelected (selectedKey, e) { const props = this.$props if (props.disabled || this.isChildDisabled(selectedKey)) { return } // Do not trigger Trigger popup if (e && e.stopPropagation) { e.stopPropagation() } const oldValue = this.$data._value const value = oldValue.filter(singleValue => { return singleValue !== selectedKey }) const canMultiple = isMultipleOrTags(props) if (canMultiple) { let event = selectedKey if (props.labelInValue) { event = { key: selectedKey, label: this.getLabelBySingleValue(selectedKey), } } this.$emit('deselect', event, this.getOptionBySingleValue(selectedKey)) } this.fireChange(value) }, openIfHasChildren () { const { $props } = this if (($props.children && $props.children.length) || isSingleMode($props)) { this.setOpenState(true) } }, fireSelect (value) { this.$emit('select', this.getVLBySingleValue(value), this.getOptionBySingleValue(value)) }, fireChange (value) { if (!hasProp(this, 'value')) { this.setState({ _value: value, }, this.forcePopupAlign) } const vls = this.getVLForOnChange(value) const options = this.getOptionsBySingleValue(value) this._valueOptions = options this.$emit('change', vls, isMultipleOrTags(this.$props) ? options : options[0]) }, isChildDisabled (key) { return (this.$props.children || []).some(child => { const childValue = getValuePropValue(child) return childValue === key && getValue(child, 'disabled') }) }, forcePopupAlign () { if (!this.$data._open) { return } if (this.selectTriggerRef && this.selectTriggerRef.triggerRef) { this.selectTriggerRef.triggerRef.forcePopupAlign() } }, renderFilterOptions () { const { _inputValue: inputValue } = this.$data const { children, tags, filterOption, notFoundContent } = this.$props const menuItems = [] const childrenKeys = [] let options = this.renderFilterOptionsFromChildren( children, childrenKeys, menuItems, ) if (tags) { // tags value must be string let value = this.$data._value value = value.filter(singleValue => { return ( childrenKeys.indexOf(singleValue) === -1 && (!inputValue || String(singleValue).indexOf(String(inputValue)) > -1) ) }) value.forEach(singleValue => { const key = singleValue const menuItem = ( {key} ) options.push(menuItem) menuItems.push(menuItem) }) if (inputValue) { const notFindInputItem = menuItems.every(option => { // this.filterOption return true has two meaning, // 1, some one exists after filtering // 2, filterOption is set to false // condition 2 does not mean the option has same value with inputValue const filterFn = () => getValuePropValue(option) === inputValue if (filterOption !== false) { return !this._filterOption( inputValue, option, filterFn ) } return !filterFn() }) if (notFindInputItem) { const p = { attrs: UNSELECTABLE_ATTRIBUTE, key: inputValue, props: { value: inputValue, role: 'option', }, style: UNSELECTABLE_STYLE, } options.unshift( {inputValue} ) } } } if (!options.length && notFoundContent) { const p = { attrs: UNSELECTABLE_ATTRIBUTE, key: 'NOT_FOUND', props: { value: 'NOT_FOUND', disabled: true, role: 'option', }, style: UNSELECTABLE_STYLE, } options = [ {notFoundContent} , ] } return options }, renderFilterOptionsFromChildren (children = [], childrenKeys, menuItems) { const sel = [] const props = this.$props const { _inputValue: inputValue } = this.$data const tags = props.tags children.forEach(child => { if (!child.data || child.data.slot !== undefined) { return } if (getSlotOptions(child).isSelectOptGroup) { 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 ( {subChild.componentOptions.children} ) }, ) sel.push( {innerItems} , ) // Not match } else { const innerItems = this.renderFilterOptionsFromChildren( childChildren, childrenKeys, menuItems, ) if (innerItems.length) { sel.push( {innerItems} ) } } return } warning( getSlotOptions(child).isSelectOption, 'the children of `Select` should be `Select.Option` or `Select.OptGroup`, ' + `instead of \`${getSlotOptions(child).name || getSlotOptions(child)}\`.` ) const childValue = getValuePropValue(child) validateOptionValue(childValue, this.$props) if (this._filterOption(inputValue, child)) { const p = { attrs: { ...UNSELECTABLE_ATTRIBUTE, ...getAttrs(child), }, key: childValue, props: { value: childValue, ...getPropsData(child), role: 'option', }, style: UNSELECTABLE_STYLE, on: getEvents(child), class: getClass(child), } const menuItem = ( {child.componentOptions.children} ) sel.push(menuItem) menuItems.push(menuItem) } if (tags) { childrenKeys.push(childValue) } }) return sel }, renderTopControlNode () { const { $props: props } = this const { _value: value, _inputValue: inputValue, _open: open } = this.$data const { choiceTransitionName, prefixCls, maxTagTextLength, maxTagCount, maxTagPlaceholder, showSearch, } = props const removeIcon = getComponentFromProp(this, 'removeIcon') const className = `${prefixCls}-selection__rendered` // search input is inside topControlNode in single, multiple & combobox. 2016/04/13 let innerNode = null if (isSingleMode(props)) { let selectedValue = null if (value.length) { let showSelectedValue = false let opacity = 1 if (!showSearch) { showSelectedValue = true } else if (open) { showSelectedValue = !inputValue if (showSelectedValue) { opacity = 0.4 } } else { showSelectedValue = true } const singleValue = value[0] const { label, title } = this.getOptionInfoBySingleValue(singleValue) selectedValue = (
{label}
) } if (!showSearch) { innerNode = [selectedValue] } else { innerNode = [ selectedValue, , ] } } else { let selectedValueNodes = [] let limitedCountValue = value let maxTagPlaceholderEl if (maxTagCount !== undefined && value.length > maxTagCount) { limitedCountValue = limitedCountValue.slice(0, maxTagCount) const omittedValues = this.getVLForOnChange(value.slice(maxTagCount, value.length)) let content = `+ ${value.length - maxTagCount} ...` if (maxTagPlaceholder) { content = typeof maxTagPlaceholder === 'function' ? maxTagPlaceholder(omittedValues) : maxTagPlaceholder } maxTagPlaceholderEl = () } if (isMultipleOrTags(props)) { selectedValueNodes = limitedCountValue.map(singleValue => { const info = this.getOptionInfoBySingleValue(singleValue) let content = info.label const title = info.title || content if ( maxTagTextLength && typeof content === 'string' && content.length > maxTagTextLength ) { content = `${content.slice(0, maxTagTextLength)}...` } const disabled = this.isChildDisabled(singleValue) const choiceClassName = disabled ? `${prefixCls}-selection__choice ${prefixCls}-selection__choice__disabled` : `${prefixCls}-selection__choice` return ( ) }) } if (maxTagPlaceholderEl) { selectedValueNodes.push(maxTagPlaceholderEl) } selectedValueNodes.push( ) if (isMultipleOrTags(props) && choiceTransitionName) { const transitionProps = getTransitionProps(choiceTransitionName, { tag: 'ul', afterLeave: this.onChoiceAnimationLeave, }) innerNode = ( {selectedValueNodes} ) } else { innerNode = } } return (
{this.getPlaceholderElement()} {innerNode}
) }, 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 ? ( ) : ( ) return ( {inputIcon || defaultIcon} ) }, topCtrlContainerClick (e) { if (this.$data._open && !isSingleMode(this.$props)) { e.stopPropagation() } }, renderClear () { const { prefixCls, allowClear } = this.$props const { _value: value, _inputValue: inputValue } = this.$data const clearIcon = getComponentFromProp(this, 'clearIcon') const clear = ( {clearIcon || ×} ) if (!allowClear) { return null } if (isCombobox(this.$props)) { if (inputValue) { return clear } return null } if (inputValue || value.length) { return clear } return null }, selectionRefClick (e) { e.stopPropagation() if (!this.disabled) { const input = this.getInputDOMNode() if (this._focused && this.$data._open) { this._focused = false this.setOpenState(false, false) input && input.blur() } else { this.clearBlurTime() this._focused = true this.setOpenState(true, true) input && input.focus() } } }, selectionRefFocus (e) { if (this._focused || this.disabled) { return } this._focused = true this.updateFocusClassName() this.$emit('focus') }, selectionRefBlur (e) { this._focused = false this.updateFocusClassName() this.$emit('blur') }, }, render () { const props = this.$props const multiple = isMultipleOrTags(props) const state = this.$data const { disabled, prefixCls } = props const ctrlNode = this.renderTopControlNode() const { _open: open, _inputValue: inputValue, _value: value } = this.$data if (open) { this._options = this.renderFilterOptions() } const realOpen = this.getRealOpenState() const options = this._options || [] const { $listeners } = this const { mouseenter = noop, mouseleave = noop, popupScroll = noop } = $listeners const selectionProps = { props: {}, attrs: { role: 'combobox', 'aria-autocomplete': 'list', 'aria-haspopup': 'true', 'aria-expanded': realOpen, 'aria-controls': this.ariaId, }, on: { click: this.selectionRefClick, }, class: `${prefixCls}-selection ${prefixCls}-selection--${multiple ? 'multiple' : 'single'}`, directives: [{ name: 'ant-ref', value: this.saveSelectionRef, }], key: 'selection', } if (!isMultipleOrTagsOrCombobox(props)) { selectionProps.on.keydown = this.onKeyDown selectionProps.on.focus = this.selectionRefFocus selectionProps.on.blur = this.selectionRefBlur selectionProps.attrs.tabIndex = props.disabled ? -1 : props.tabIndex } const rootCls = { [prefixCls]: true, [`${prefixCls}-open`]: open, [`${prefixCls}-focused`]: open || !!this._focused, [`${prefixCls}-combobox`]: isCombobox(props), [`${prefixCls}-disabled`]: disabled, [`${prefixCls}-enabled`]: !disabled, [`${prefixCls}-allow-clear`]: !!props.allowClear, [`${prefixCls}-no-arrow`]: !props.showArrow, } return (
{ctrlNode} {this.renderClear()} {this.renderArrow(!!multiple)}
) }, } export { Select } export default proxyComponent(Select)