ant-design-vue/components/vc-select/Select.vue

1398 lines
39 KiB
Vue

<script>
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 { hasProp, getSlotOptions } from '../_util/props-util'
import getTransitionProps from '../_util/getTransitionProps'
import { cloneElement, getClass, getPropsData, getValueByProp as getValue, getEvents } from '../_util/vnode'
import BaseMixin from '../_util/BaseMixin'
import {
getPropValue,
getValuePropValue,
isCombobox,
isMultipleOrTags,
isMultipleOrTagsOrCombobox,
isSingleMode,
toArray,
findIndexInValueByKey,
UNSELECTABLE_ATTRIBUTE,
UNSELECTABLE_STYLE,
preventDefaultEvent,
findFirstMenuItem,
includesSeparators,
splitBySeparators,
findIndexInValueByLabel,
defaultFilterFn,
validateOptionValue,
} from './util'
import SelectTrigger from './SelectTrigger'
import { SelectPropTypes } from './PropTypes'
import { setTimeout } from 'timers'
function noop () {}
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)
}
}
}
}
export default {
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.string.def('Not Found'),
backfill: PropTypes.bool.def(false),
showAction: SelectPropTypes.showAction.def(['click']),
combobox: PropTypes.bool.def(false),
// onChange: noop,
// onFocus: noop,
// onBlur: noop,
// onSelect: noop,
// onSearch: noop,
// onDeselect: noop,
// onInputKeydown: noop,
},
data () {
let sValue = []
const { value, defaultValue, combobox, open, defaultOpen, $slots } = this
if (hasProp(this, 'value')) {
sValue = toArray(value)
} else {
sValue = toArray(defaultValue)
}
sValue = this.addLabelToValue(sValue)
sValue = this.addTitleToValue($slots, sValue)
let inputValue = ''
if (combobox) {
inputValue = sValue.length
? this.getLabelFromProps(sValue[0].key)
: ''
}
let sOpen = open
if (sOpen === undefined) {
sOpen = defaultOpen
}
return {
sValue,
inputValue,
sOpen,
}
},
beforeMount () {
// this.adjustOpenState()
},
mounted () {
this.$nextTick(() => {
this.autoFocus && this.focus()
})
},
watch: {
'$props': {
handler: function (nextProps) {
if (hasProp(this, 'value')) {
const { combobox, $slots } = this
let value = toArray(this.value)
value = this.addLabelToValue(value)
value = this.addTitleToValue($slots, value)
this.setState({
sValue: value,
})
if (combobox) {
this.setState({
inputValue: value.length
? this.getLabelFromProps(value[0].key)
: '',
})
}
}
this.adjustOpenState()
},
deep: true,
},
},
updated () {
this.$nextTick(() => {
if (isMultipleOrTags(this.$props)) {
const inputNode = this.getInputDOMNode()
const mirrorNode = this.getInputMirrorDOMNode()
if (inputNode.value) {
inputNode.style.width = ''
inputNode.style.width = `${mirrorNode.clientWidth}px`
} else {
inputNode.style.width = ''
}
}
})
},
beforeUpdate () {
// console.log('beforeUpdate')
// this.adjustOpenState()
},
beforeDestroy () {
this.clearFocusTime()
this.clearBlurTime()
this.clearAdjustTimer()
if (this.dropdownContainer) {
// ReactDOM.unmountComponentAtNode(this.dropdownContainer)
document.body.removeChild(this.dropdownContainer)
this.dropdownContainer = null
}
},
methods: {
onInputChange (event) {
const { tokenSeparators } = this
const val = event.target.value
if (
isMultipleOrTags(this.$props) &&
tokenSeparators &&
includesSeparators(val, tokenSeparators)
) {
const nextValue = this.tokenize(val)
this.fireChange(nextValue)
this.setOpenState(false, true)
this.setInputValue('', false)
return
}
this.setInputValue(val)
this.setState({
sOpen: true,
})
if (isCombobox(this.$props)) {
this.fireChange([
{
key: val,
},
])
}
},
onDropdownVisibleChange (open) {
if (open && !this._focused) {
this.clearBlurTime()
this.timeoutFocus()
this._focused = true
this.updateFocusClassName()
}
this.setOpenState(open)
},
// combobox ignore
onKeyDown (event) {
const { disabled, openStatus } = this
if (disabled) {
return
}
const keyCode = event.keyCode
if (openStatus && !this.getInputDOMNode()) {
this.onInputKeydown(event)
} else if (keyCode === KeyCode.ENTER || keyCode === KeyCode.DOWN) {
this.setOpenState(true)
event.preventDefault()
}
},
onInputKeydown (event) {
const { disabled, openStatus, sValue, $props } = this
if (disabled) {
return
}
const keyCode = event.keyCode
if (
isMultipleOrTags($props) &&
!event.target.value &&
keyCode === KeyCode.BACKSPACE
) {
event.preventDefault()
if (sValue.length) {
this.removeSelected(sValue[sValue.length - 1].key)
}
return
}
if (keyCode === KeyCode.DOWN) {
if (!openStatus) {
this.openIfHasChildren()
event.preventDefault()
event.stopPropagation()
return
}
} else if (keyCode === KeyCode.ESC) {
if (openStatus) {
this.setOpenState(false)
event.preventDefault()
event.stopPropagation()
}
return
}
if (openStatus) {
const menu = this.$refs.selectTriggerRef.getInnerMenu()
if (menu && menu.onKeyDown(event, this.handleBackfill)) {
event.preventDefault()
event.stopPropagation()
}
}
},
onMenuSelect ({ item }) {
let sValue = this.sValue
const props = this.$props
const selectedValue = getValuePropValue(item)
const selectedLabel = this.getLabelFromOption(item)
const lastValue = sValue[sValue.length - 1]
let event = selectedValue
if (props.labelInValue) {
event = {
key: event,
label: selectedLabel,
}
}
this.__emit('select', event, item)
const selectedTitle = item.title
if (isMultipleOrTags(props)) {
if (findIndexInValueByKey(sValue, selectedValue) !== -1) {
return
}
sValue = sValue.concat([
{
key: selectedValue,
label: selectedLabel,
title: selectedTitle,
},
])
} else {
if (isCombobox(props)) {
this.skipAdjustOpen = true
this.clearAdjustTimer()
this.skipAdjustOpenTimer = setTimeout(() => {
this.skipAdjustOpen = false
}, 0)
}
if (lastValue && lastValue.key === selectedValue && !lastValue.backfill) {
this.setOpenState(false, true)
return
}
sValue = [
{
key: selectedValue,
label: selectedLabel,
title: selectedTitle,
},
]
this.setOpenState(false, true)
}
this.fireChange(sValue)
let inputValue
if (isCombobox(props)) {
inputValue = getPropValue(item, props.optionLabelProp)
} else {
inputValue = ''
}
this.setInputValue(inputValue, false)
},
onMenuDeselect ({ item, domEvent }) {
if (domEvent.type === 'click') {
this.removeSelected(getValuePropValue(item))
}
this.setInputValue('', false)
},
onArrowClick (e) {
// e.stopPropagation()
// if (!this.disabled) {
// this.setOpenState(!this.openStatus, !this.openStatus)
// }
},
onPlaceholderClick (e) {
if (this._focused) {
e.stopPropagation()
}
if (this.getInputDOMNode()) {
this.getInputDOMNode().focus()
}
},
onOuterFocus (e) {
console.log('onOuterFocus')
if (this.disabled) {
e.preventDefault()
return
}
this.clearBlurTime()
if (
!isMultipleOrTagsOrCombobox(this.$props) &&
e.target === this.getInputDOMNode()
) {
return
}
if (this._focused) {
return
}
this._focused = true
this.updateFocusClassName()
this.timeoutFocus()
},
onPopupFocus () {
// fix ie scrollbar, focus element again
this.maybeFocus(true, true)
},
onOuterBlur (e) {
if (this.disabled) {
e.preventDefault()
return
}
this.blurTimer = setTimeout(() => {
this._focused = false
this.updateFocusClassName()
const props = this.$props
let { sValue } = this
const { inputValue } = this
if (
isSingleMode(props) &&
props.showSearch &&
inputValue &&
props.defaultActiveFirstOption
) {
const options = this._options || []
if (options.length) {
const firstOption = findFirstMenuItem(options)
if (firstOption) {
sValue = [
{
key: firstOption.key,
label: this.getLabelFromOption(firstOption),
},
]
this.fireChange(sValue)
}
}
} else if (isMultipleOrTags(props) && inputValue) {
// why not use setState?
this.inputValue = this.getInputDOMNode().value = ''
}
this.__emit('blur', this.getVLForOnChange(sValue))
this.setOpenState(false)
}, 10)
},
onClearSelection (event) {
const { inputValue, sValue, disabled } = this
if (disabled) {
return
}
event.stopPropagation()
if (inputValue || sValue.length) {
if (sValue.length) {
this.fireChange([])
}
this.setOpenState(false, true)
if (inputValue) {
this.setInputValue('')
}
}
},
onChoiceAnimationLeave () {
this.$refs.selectTriggerRef.triggerRef.forcePopupAlign()
},
getLabelBySingleValue (children, value) {
if (value === undefined) {
return null
}
let label = null
children.forEach(child => {
if (!child) {
return
}
if (getSlotOptions(child).isSelectOptGroup) {
const maybe = this.getLabelBySingleValue(child.componentOptions.children, value)
if (maybe !== null) {
label = maybe
}
} else if (getValuePropValue(child) === value) {
label = this.getLabelFromOption(child)
}
})
return label
},
getValueByLabel (children, label) {
if (label === undefined) {
return null
}
let value = null
children.forEach(child => {
if (!child) {
return
}
if (getSlotOptions(child).isSelectOptGroup) {
const maybe = this.getValueByLabel(child.componentOptions.children, label)
if (maybe !== null) {
value = maybe
}
} else if (toArray(this.getLabelFromOption(child)).join('') === label) {
value = getValuePropValue(child)
}
})
return value
},
getLabelFromOption (child) {
return getPropValue(child, this.optionLabelProp)
},
getLabelFromProps (value) {
return this.getLabelByValue(this.$slots.default, value)
},
getVLForOnChange (vls_) {
let vls = vls_
if (vls !== undefined) {
if (!this.labelInValue) {
vls = vls.map(v => v.key)
} else {
vls = vls.map(vl => ({ key: vl.key, label: vl.label }))
}
return isMultipleOrTags(this.$props) ? vls : vls[0]
}
return vls
},
getLabelByValue (children, value) {
const label = this.getLabelBySingleValue(children, value)
if (label === null) {
return value
}
return label
},
getDropdownContainer () {
if (!this.dropdownContainer) {
this.dropdownContainer = document.createElement('div')
document.body.appendChild(this.dropdownContainer)
}
return this.dropdownContainer
},
getPlaceholderElement () {
// const { props, state } = this
const { inputValue, sValue, placeholder, prefixCls, $props } = this
let hidden = false
if (inputValue) {
hidden = true
}
if (sValue.length) {
hidden = true
}
if (isCombobox($props) && sValue.length === 1 && !sValue[0].key) {
hidden = false
}
if (placeholder) {
const p = {
props: {
},
on: {
mousedown: preventDefaultEvent,
click: this.onPlaceholderClick,
},
attrs: UNSELECTABLE_ATTRIBUTE,
style: {
display: hidden ? 'none' : 'block',
...UNSELECTABLE_STYLE,
},
class: `${prefixCls}-selection__placeholder`,
}
return (
<div {...p}>
{placeholder}
</div>
)
}
return null
},
inputClick (e) {
if (this._focused) {
e.stopPropagation()
}
},
inputBlur (e) {
// console.log(e.target)
this.clearBlurTime()
this.blurTimer = setTimeout(() => {
this.onOuterBlur()
if (!this.disabled) {
this.setOpenState(!this.openStatus, !this.openStatus)
}
}, 10)
},
_getInputElement () {
const props = this.$props
const inputElement = props.getInputElement
? props.getInputElement()
: <input id={props.id} autoComplete='off' />
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
return (
<div class={`${props.prefixCls}-search__field__wrap`}>
{cloneElement(inputElement, {
attrs: {
value: this.inputValue,
disabled: props.disabled,
},
class: inputCls,
ref: 'inputRef',
on: {
input: this.onInputChange,
keydown: chaining(
this.onInputKeydown,
inputEvents.keydown || noop,
this.$listeners.inputKeydown
),
// focus: chaining(
// this.onOuterFocus,
// inputEvents.focus || noop,
// ),
blur: chaining(
this.inputBlur,
inputEvents.blur || noop,
),
click: chaining(
this.inputClick,
inputEvents.click || noop,
),
},
})}
<span
ref='inputMirrorRef'
class={`${props.prefixCls}-search__field__mirror`}
>
{this.inputValue}&nbsp;
</span>
</div>
)
},
getInputDOMNode () {
return this.$refs.topCtrlRef
? this.$refs.topCtrlRef.querySelector('input,textarea,div[contentEditable]')
: this.$refs.inputRef
},
getInputMirrorDOMNode () {
return this.$refs.inputMirrorRef
},
getPopupDOMNode () {
return this.$refs.selectTriggerRef.getPopupDOMNode()
},
getPopupMenuComponent () {
return this.$refs.selectTriggerRef.getInnerMenu()
},
setOpenState (open, needFocus) {
const { $props: props, openStatus } = this
if (openStatus === open) {
this.maybeFocus(open, needFocus)
return
}
const nextState = {
sOpen: open,
}
// clear search input value when open is false in singleMode.
if (!open && isSingleMode(props) && props.showSearch) {
this.setInputValue('')
}
if (!open) {
this.maybeFocus(open, needFocus)
}
this.setState(nextState, () => {
if (open) {
this.maybeFocus(open, needFocus)
}
})
},
setInputValue (inputValue, fireSearch = true) {
if (inputValue !== this.inputValue) {
this.setState({
inputValue,
})
if (fireSearch) {
this.__emit('search', inputValue)
}
}
},
focus () {
if (isSingleMode(this.$props)) {
this.$refs.selectionRef.focus()
} else {
this.getInputDOMNode().focus()
}
},
blur () {
if (isSingleMode(this.$props)) {
this.$refs.selectionRef.blur()
} else {
this.getInputDOMNode().blur()
}
},
handleBackfill (item) {
if (!this.backfill || !(isSingleMode(this.$props) || isCombobox(this.$props))) {
return
}
const key = getValuePropValue(item)
const label = this.getLabelFromOption(item)
const backfillValue = {
key,
label,
backfill: true,
}
if (isCombobox(this.$props)) {
this.setInputValue(key, false)
}
this.setState({
sValue: [backfillValue],
})
},
_filterOption (input, child, defaultFilter = defaultFilterFn) {
const { sValue } = this
const lastValue = sValue[sValue.length - 1]
if (!input || (lastValue && lastValue.backfill)) {
return true
}
let filterFn = this.filterOption
if (hasProp(this, 'filterOption')) {
if (this.filterOption === true) {
filterFn = defaultFilter
}
} else {
filterFn = defaultFilter
}
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 = setTimeout(() => {
this.__emit('focus')
}, 10)
},
clearFocusTime () {
if (this.focusTimer) {
clearTimeout(this.focusTimer)
this.focusTimer = null
}
},
clearBlurTime () {
if (this.blurTimer) {
clearTimeout(this.blurTimer)
this.blurTimer = null
}
},
clearAdjustTimer () {
if (this.skipAdjustOpenTimer) {
clearTimeout(this.skipAdjustOpenTimer)
this.skipAdjustOpenTimer = null
}
},
updateFocusClassName () {
const { $refs: { 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.$refs.selectionRef) {
this.$refs.selectionRef.focus()
this._focused = true
}
}
}
},
addLabelToValue (value_) {
let value = value_
if (this.labelInValue) {
value.forEach(v => {
v.label = v.label || this.getLabelFromProps(v.key)
})
} else {
value = value.map(v => {
return {
key: v,
label: this.getLabelFromProps(v),
}
})
}
return value
},
addTitleToValue ($slots, values) {
let nextValues = values
const keys = values.map(v => v.key)
$slots.default.forEach(child => {
if (!child) {
return
}
if (getSlotOptions(child).isSelectOptGroup) {
nextValues = this.addTitleToValue(child.$slots, nextValues)
} else {
const value = getValuePropValue(child)
const valueIndex = keys.indexOf(value)
if (valueIndex > -1) {
nextValues[valueIndex].title = getValue(child, 'title')
}
}
})
return nextValues
},
removeSelected (selectedKey) {
const props = this.$props
if (props.disabled || this.isChildDisabled(selectedKey)) {
return
}
let label
const value = this.sValue.filter(singleValue => {
if (singleValue.key === selectedKey) {
label = singleValue.label
}
return singleValue.key !== selectedKey
})
const canMultiple = isMultipleOrTags(props)
if (canMultiple) {
let event = selectedKey
if (props.labelInValue) {
event = {
key: selectedKey,
label,
}
}
this.__emit('deselect', event)
}
this.fireChange(value)
},
openIfHasChildren () {
const { $props, $slots } = this
if (($slots.default && $slots.default.length) || isSingleMode($props)) {
this.setOpenState(true)
}
},
fireChange (value) {
if (hasProp(this, 'value')) {
this.setState({
sValue: value,
})
}
this.__emit('change', this.getVLForOnChange(value))
},
isChildDisabled (key) {
return this.$slots.default.some(child => {
const childValue = getValuePropValue(child)
return childValue === key && getValue(child, 'title')
})
},
tokenize (string) {
const { multiple, tokenSeparators, $slots } = this
let nextValue = this.sValue
splitBySeparators(string, tokenSeparators).forEach(label => {
const selectedValue = { key: label, label }
if (findIndexInValueByLabel(nextValue, label) === -1) {
if (multiple) {
const value = this.getValueByLabel($slots.default, label)
if (value) {
selectedValue.key = value
nextValue = nextValue.concat(selectedValue)
}
} else {
nextValue = nextValue.concat(selectedValue)
}
}
})
return nextValue
},
adjustOpenState () {
if (this.skipAdjustOpen) {
return
}
const { $props, showSearch } = this
let sOpen = this.sOpen
let options = []
// If hidden menu due to no options, then it should be calculated again
if (sOpen || this.hiddenForNoOptions) {
options = this.renderFilterOptions()
console.log('options', options)
}
console.log('options1', options)
this._options = options
if (isMultipleOrTagsOrCombobox($props) || !showSearch) {
if (sOpen && !options.length) {
sOpen = false
this.hiddenForNoOptions = true
}
// Keep menu open if there are options and hidden for no options before
if (this.hiddenForNoOptions && options.length) {
sOpen = true
this.hiddenForNoOptions = false
}
}
this.sOpen = sOpen
},
getOptionsAndOpenStatus () {
let sOpen = this.sOpen
if (this.skipAdjustOpen) {
return {
option: this._options,
open: sOpen,
}
}
const { $props, showSearch } = this
let options = []
// If hidden menu due to no options, then it should be calculated again
if (true || sOpen || this.hiddenForNoOptions) {
options = this.renderFilterOptions()
}
this._options = options
if (isMultipleOrTagsOrCombobox($props) || !showSearch) {
if (sOpen && !options.length) {
sOpen = false
this.hiddenForNoOptions = true
}
// Keep menu open if there are options and hidden for no options before
if (this.hiddenForNoOptions && options.length) {
sOpen = true
this.hiddenForNoOptions = false
}
}
this.openStatus = sOpen
return {
options,
open: sOpen,
}
},
renderFilterOptions () {
const { inputValue } = this
const { $slots, tags, filterOption, notFoundContent } = this
const menuItems = []
const childrenKeys = []
let options = this.renderFilterOptionsFromChildren(
$slots.default,
childrenKeys,
menuItems,
)
if (tags) {
// tags value must be string
let value = this.sValue || []
value = value.filter(singleValue => {
return (
childrenKeys.indexOf(singleValue.key) === -1 &&
(!inputValue ||
String(singleValue.key).indexOf(String(inputValue)) > -1)
)
})
value.forEach(singleValue => {
const key = singleValue.key
const menuItem = (
<MenuItem
style={UNSELECTABLE_STYLE}
{...{ attrs: UNSELECTABLE_ATTRIBUTE }}
value={key}
key={key}
>
{key}
</MenuItem>
)
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,
},
style: UNSELECTABLE_STYLE,
}
options.unshift(
<MenuItem {...p}>
{inputValue}
</MenuItem>
)
}
}
}
if (!options.length && notFoundContent) {
const p = {
attrs: UNSELECTABLE_ATTRIBUTE,
key: 'NOT_FOUND',
props: {
value: 'NOT_FOUND',
disabled: true,
},
style: UNSELECTABLE_STYLE,
}
options = [
<MenuItem {...p}>
{notFoundContent}
</MenuItem>,
]
}
return options
},
renderFilterOptionsFromChildren (children, childrenKeys, menuItems) {
const sel = []
const props = this.$props
const { inputValue } = this
const tags = props.tags
children.forEach(child => {
if (!child) {
return
}
if (getSlotOptions(child).isSelectOptGroup) {
const innerItems = this.renderFilterOptionsFromChildren(
child.componentOptions.children,
childrenKeys,
menuItems,
)
if (innerItems.length) {
let label = getValue(child, 'label')
let key = child.key
if (!key && typeof label === 'string') {
key = label
} else if (!label && key) {
label = key
}
sel.push(
<MenuItemGroup key={key} title={label}>
{innerItems}
</MenuItemGroup>
)
}
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,
key: childValue,
props: {
value: childValue,
...getPropsData(child),
},
style: UNSELECTABLE_STYLE,
on: getEvents(child),
}
const menuItem = (
<MenuItem {...p}>{child.componentOptions.children}</MenuItem>
)
sel.push(menuItem)
menuItems.push(menuItem)
}
if (tags && !getValue(child, 'disabled')) {
childrenKeys.push(childValue)
}
})
return sel
},
renderTopControlNode (openStatus) {
const { sValue, inputValue, $props: props } = this
const {
choiceTransitionName,
prefixCls,
maxTagTextLength,
maxTagCount,
maxTagPlaceholder,
showSearch,
} = props
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 (sValue.length) {
let showSelectedValue = false
let opacity = 1
if (!showSearch) {
showSelectedValue = true
} else {
if (openStatus) {
showSelectedValue = !inputValue
if (showSelectedValue) {
opacity = 0.4
}
} else {
showSelectedValue = true
}
}
const singleValue = sValue[0]
selectedValue = (
<div
key='value'
class={`${prefixCls}-selection-selected-value`}
title={singleValue.title || singleValue.label}
style={{
display: showSelectedValue ? 'block' : 'none',
opacity,
}}
>
{sValue[0].label}
</div>
)
}
if (!showSearch) {
innerNode = [selectedValue]
} else {
innerNode = [
selectedValue,
<div
class={`${prefixCls}-search ${prefixCls}-search--inline`}
key='input'
style={{
display: openStatus ? 'block' : 'none',
}}
>
{this._getInputElement()}
</div>,
]
}
} else {
let selectedValueNodes = []
let limitedCountValue = sValue
let maxTagPlaceholderEl
if (maxTagCount !== undefined && sValue.length > maxTagCount) {
limitedCountValue = limitedCountValue.slice(0, maxTagCount)
const omittedValues = this.getVLForOnChange(sValue.slice(maxTagCount, sValue.length))
let content = `+ ${sValue.length - maxTagCount} ...`
if (maxTagPlaceholder) {
content = typeof maxTagPlaceholder === 'function'
? maxTagPlaceholder(omittedValues) : maxTagPlaceholder
}
maxTagPlaceholderEl = (<li
style={UNSELECTABLE_STYLE}
unselectable='unselectable'
onMousedown={preventDefaultEvent}
class={`${prefixCls}-selection__choice ${prefixCls}-selection__choice__disabled`}
key={'maxTagPlaceholder'}
title={content}
>
<div class={`${prefixCls}-selection__choice__content`}>{content}</div>
</li>)
}
if (isMultipleOrTags(props)) {
selectedValueNodes = limitedCountValue.map(singleValue => {
let content = singleValue.label
const title = singleValue.title || content
if (
maxTagTextLength &&
typeof content === 'string' &&
content.length > maxTagTextLength
) {
content = `${content.slice(0, maxTagTextLength)}...`
}
const disabled = this.isChildDisabled(singleValue.key)
const choiceClassName = disabled
? `${prefixCls}-selection__choice ${prefixCls}-selection__choice__disabled`
: `${prefixCls}-selection__choice`
return (
<li
style={UNSELECTABLE_STYLE}
unselectable='unselectable'
onMousedown={preventDefaultEvent}
class={choiceClassName}
key={singleValue.key}
title={title}
>
<div class={`${prefixCls}-selection__choice__content`}>
{content}
</div>
{disabled ? null : (
<span
class={`${prefixCls}-selection__choice__remove`}
onClick={this.removeSelected.bind(this, singleValue.key)}
/>)}
</li>
)
})
}
if (maxTagPlaceholderEl) {
selectedValueNodes.push(maxTagPlaceholderEl)
}
selectedValueNodes.push(
<li
class={`${prefixCls}-search ${prefixCls}-search--inline`}
key='__input'
>
{this._getInputElement()}
</li>
)
if (isMultipleOrTags(props) && choiceTransitionName) {
const transitionProps = getTransitionProps(choiceTransitionName, {
tag: 'ul',
// beforeEnter: this.onChoiceAnimationLeave,
})
innerNode = (
<transition-group
// onLeave={this.onChoiceAnimationLeave}
// component='ul'
// transitionName={choiceTransitionName}
{...transitionProps}
>
{selectedValueNodes}
</transition-group>
)
} else {
innerNode = (
<ul>
{selectedValueNodes}
</ul>
)
}
}
return (
<div class={className} ref='topCtrlRef'>
{this.getPlaceholderElement()}
{innerNode}
</div>
)
},
renderClear () {
const { prefixCls, allowClear, sValue, inputValue } = this
const clear = (
<span
key='clear'
onMousedown={preventDefaultEvent}
style={UNSELECTABLE_STYLE}
unselectable='unselectable'
class={`${prefixCls}-selection__clear`}
onClick={this.onClearSelection}
/>
)
if (!allowClear) {
return null
}
if (isCombobox(this.$props)) {
if (inputValue) {
return clear
}
return null
}
if (inputValue || sValue.length) {
return clear
}
return null
},
rootRefClick (e) {
// e.stopPropagation()
if (this._focused) {
// this.getInputDOMNode().blur()
this.onOuterBlur()
} else {
this.onOuterFocus()
// this.getInputDOMNode().focus()
}
},
},
render () {
const props = this.$props
const multiple = isMultipleOrTags(props)
const { options, open: openStatus } = this.getOptionsAndOpenStatus()
const { disabled, prefixCls, inputValue, sValue, $listeners } = this
const { mouseenter = noop, mouseleave = noop, popupScroll = noop } = $listeners
const ctrlNode = this.renderTopControlNode(openStatus)
let extraSelectionProps = {}
if (!isMultipleOrTagsOrCombobox(props)) {
extraSelectionProps = {
onKeyDown: this.onKeyDown,
tabIndex: props.disabled ? -1 : 0,
}
}
const rootCls = {
[prefixCls]: 1,
[`${prefixCls}-open`]: openStatus,
[`${prefixCls}-focused`]: openStatus || !!this._focused,
[`${prefixCls}-combobox`]: isCombobox(props),
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-enabled`]: !disabled,
[`${prefixCls}-allow-clear`]: !!props.allowClear,
}
console.log(options)
return (
<SelectTrigger
dropdownAlign={props.dropdownAlign}
dropdownClass={props.dropdownClassName}
dropdownMatchSelectWidth={props.dropdownMatchSelectWidth}
defaultActiveFirstOption={props.defaultActiveFirstOption}
dropdownMenuStyle={props.dropdownMenuStyle}
transitionName={props.transitionName}
animation={props.animation}
prefixCls={props.prefixCls}
dropdownStyle={props.dropdownStyle}
combobox={props.combobox}
showSearch={props.showSearch}
options={options}
multiple={multiple}
disabled={disabled}
visible={openStatus}
inputValue={inputValue}
value={sValue}
firstActiveValue={props.firstActiveValue}
onDropdownVisibleChange={this.onDropdownVisibleChange}
getPopupContainer={props.getPopupContainer}
onMenuSelect={this.onMenuSelect}
onMenuDeselect={this.onMenuDeselect}
onPopupScroll={popupScroll}
onPopupFocus={this.onPopupFocus}
onMouseenter={mouseenter}
onMouseleave={mouseleave}
showAction={props.showAction}
ref='selectTriggerRef'
>
<div
ref='rootRef'
// onBlur={this.onOuterBlur}
// onFocus={this.onOuterFocus}
onClick={this.rootRefClick}
class={classnames(rootCls)}
>
<div
ref='selectionRef'
key='selection'
class={`${prefixCls}-selection
${prefixCls}-selection--${multiple ? 'multiple' : 'single'}`}
role='combobox'
aria-autocomplete='list'
aria-haspopup='true'
aria-expanded={openStatus}
{...extraSelectionProps}
// onClick={this.stopPropagation}
>
{ctrlNode}
{this.renderClear()}
{multiple || !props.showArrow ? null : (
<span
key='arrow'
class={`${prefixCls}-arrow`}
style={UNSELECTABLE_STYLE}
unselectable='unselectable'
onClick={this.onArrowClick}
>
<b />
</span>)}
</div>
</div>
</SelectTrigger>
)
},
}
</script>