1046 lines
32 KiB
Vue
1046 lines
32 KiB
Vue
import PropTypes from '../../_util/vue-types'
|
|
import KeyCode from '../../_util/KeyCode'
|
|
import classnames from 'classnames'
|
|
import pick from 'lodash/pick'
|
|
import omit from 'omit.js'
|
|
import {
|
|
getPropValue, getValuePropValue,
|
|
isMultiple, toArray,
|
|
UNSELECTABLE_ATTRIBUTE, UNSELECTABLE_STYLE,
|
|
preventDefaultEvent,
|
|
getTreeNodesStates, flatToHierarchy, filterParentPosition,
|
|
isPositionPrefix, labelCompatible, loopAllChildren, filterAllCheckedData,
|
|
processSimpleTreeData, toTitle,
|
|
} from './util'
|
|
import SelectTrigger from './SelectTrigger'
|
|
import _TreeNode from './TreeNode'
|
|
import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies'
|
|
import { SelectPropTypes } from './PropTypes'
|
|
import { initDefaultProps, getOptionProps, hasProp, getAllProps, getComponentFromProp } from '../../_util/props-util'
|
|
import BaseMixin from '../../_util/BaseMixin'
|
|
import getTransitionProps from '../../_util/getTransitionProps'
|
|
|
|
function noop () {
|
|
}
|
|
|
|
function filterFn (input, child) {
|
|
return String(getPropValue(child, labelCompatible(this.$props.treeNodeFilterProp)))
|
|
.indexOf(input) > -1
|
|
}
|
|
|
|
const defaultProps = {
|
|
prefixCls: 'rc-tree-select',
|
|
// filterTreeNode: filterFn, // [Legacy] TODO: Set false and filter not hide?
|
|
showSearch: true,
|
|
allowClear: false,
|
|
// placeholder: '',
|
|
// searchPlaceholder: '',
|
|
labelInValue: false,
|
|
// onClick: noop,
|
|
// onChange: noop,
|
|
// onSelect: noop,
|
|
// onDeselect: noop,
|
|
// onSearch: noop,
|
|
showArrow: true,
|
|
dropdownMatchSelectWidth: true,
|
|
dropdownStyle: {},
|
|
dropdownVisibleChange: () => { return true },
|
|
notFoundContent: 'Not Found',
|
|
showCheckedStrategy: SHOW_CHILD,
|
|
// skipHandleInitValue: false, // Deprecated (use treeCheckStrictly)
|
|
treeCheckStrictly: false,
|
|
treeIcon: false,
|
|
treeLine: false,
|
|
treeDataSimpleMode: false,
|
|
treeDefaultExpandAll: false,
|
|
treeCheckable: false,
|
|
treeNodeFilterProp: 'value',
|
|
treeNodeLabelProp: 'title',
|
|
}
|
|
|
|
const Select = {
|
|
mixins: [BaseMixin],
|
|
name: 'VCTreeSelect',
|
|
props: initDefaultProps({ ...SelectPropTypes, __propsSymbol__: PropTypes.any }, defaultProps),
|
|
data () {
|
|
let value = []
|
|
const props = getOptionProps(this)
|
|
this.preProps = { ...props }
|
|
if ('value' in props) {
|
|
value = toArray(props.value)
|
|
} else {
|
|
value = toArray(props.defaultValue)
|
|
}
|
|
// save parsed treeData, for performance (treeData may be very big)
|
|
this.renderedTreeData = this.renderTreeData()
|
|
value = this.addLabelToValue(props, value)
|
|
value = this.getValue(props, value, props.inputValue ? '__strict' : true)
|
|
const inputValue = props.inputValue || ''
|
|
// if (props.combobox) {
|
|
// inputValue = value.length ? String(value[0].value) : '';
|
|
// }
|
|
return {
|
|
sValue: value,
|
|
sInputValue: inputValue,
|
|
sOpen: props.open || props.defaultOpen,
|
|
sFocused: false,
|
|
}
|
|
},
|
|
|
|
mounted () {
|
|
this.$nextTick(() => {
|
|
const { autoFocus, disabled } = this
|
|
if (isMultiple(this.$props)) {
|
|
const inputNode = this.getInputDOMNode()
|
|
if (inputNode.value) {
|
|
inputNode.style.width = ''
|
|
inputNode.style.width = `${this.$refs.inputMirrorInstance.clientWidth || this.$refs.inputMirrorInstance.offsetWidth}px`
|
|
} else {
|
|
inputNode.style.width = ''
|
|
}
|
|
}
|
|
if (autoFocus && !disabled) {
|
|
this.focus()
|
|
}
|
|
})
|
|
},
|
|
watch: {
|
|
// for performance (use __propsSymbol__ avoid deep watch)
|
|
__propsSymbol__ () {
|
|
const nextProps = getOptionProps(this)
|
|
// save parsed treeData, for performance (treeData may be very big)
|
|
this.renderedTreeData = this.renderTreeData(nextProps)
|
|
// Detecting whether the object of `onChange`'s argument is old ref.
|
|
// Better to do a deep equal later.
|
|
this._cacheTreeNodesStates = this._cacheTreeNodesStates !== 'no' &&
|
|
this._savedValue &&
|
|
nextProps.value === this._savedValue
|
|
if (this.preProps.treeData !== nextProps.treeData ||
|
|
this.preProps.children !== nextProps.children) {
|
|
// refresh this._treeNodesStates cache
|
|
this._treeNodesStates = getTreeNodesStates(
|
|
this.renderedTreeData || nextProps.children,
|
|
this.sValue.map(item => item.value)
|
|
)
|
|
}
|
|
if ('value' in nextProps) {
|
|
let value = toArray(nextProps.value)
|
|
value = this.addLabelToValue(nextProps, value)
|
|
value = this.getValue(nextProps, value)
|
|
this.setState({
|
|
sValue: value,
|
|
}, this.forcePopupAlign)
|
|
// if (nextProps.combobox) {
|
|
// this.setState({
|
|
// inputValue: value.length ? String(value[0].key) : '',
|
|
// });
|
|
// }
|
|
}
|
|
if (nextProps.inputValue !== this.preProps.inputValue) {
|
|
this.setState({
|
|
sInputValue: nextProps.inputValue,
|
|
})
|
|
}
|
|
if ('open' in nextProps) {
|
|
this.setState({
|
|
sOpen: nextProps.open,
|
|
})
|
|
}
|
|
this.preProps = { ...nextProps }
|
|
},
|
|
},
|
|
|
|
beforeUpdate () {
|
|
if (this._savedValue && this.$props.value &&
|
|
this.$props.value !== this._savedValue &&
|
|
this.$props.value === this.preProps.value) {
|
|
this._cacheTreeNodesStates = false
|
|
this.getValue(this.$props, this.addLabelToValue(this.$props, toArray(this.$props.value)))
|
|
}
|
|
},
|
|
|
|
updated () {
|
|
const state = this.$data
|
|
const props = this.$props
|
|
if (state.sOpen && isMultiple(props)) {
|
|
this.$nextTick(() => {
|
|
const inputNode = this.getInputDOMNode()
|
|
if (inputNode.value) {
|
|
inputNode.style.width = ''
|
|
inputNode.style.width = `${this.$refs.inputMirrorInstance.clientWidth}px`
|
|
} else {
|
|
inputNode.style.width = ''
|
|
}
|
|
})
|
|
}
|
|
},
|
|
|
|
beforeDestroy () {
|
|
this.clearDelayTimer()
|
|
if (this.dropdownContainer) {
|
|
document.body.removeChild(this.dropdownContainer)
|
|
this.dropdownContainer = null
|
|
}
|
|
},
|
|
methods: {
|
|
loopTreeData (data, level = 0, treeCheckable) {
|
|
return data.map((item, index) => {
|
|
const pos = `${level}-${index}`
|
|
const {
|
|
label,
|
|
value,
|
|
disabled,
|
|
key,
|
|
selectable,
|
|
children,
|
|
isLeaf,
|
|
...otherProps
|
|
} = item
|
|
const tnProps = {
|
|
...pick(item, ['on', 'class', 'style']),
|
|
props: {
|
|
value,
|
|
title: label,
|
|
disabled: disabled || false,
|
|
selectable: selectable === false ? selectable : !treeCheckable,
|
|
...omit(otherProps, ['on', 'class', 'style']),
|
|
},
|
|
key: key || value || pos,
|
|
}
|
|
let ret
|
|
if (children && children.length) {
|
|
ret = (<_TreeNode {...tnProps}>{this.loopTreeData(children, pos, treeCheckable)}</_TreeNode>)
|
|
} else {
|
|
ret = (<_TreeNode {...tnProps} isLeaf={isLeaf}/>)
|
|
}
|
|
return ret
|
|
})
|
|
},
|
|
onInputChange (event) {
|
|
const val = event.target.value
|
|
const { $props: props } = this
|
|
this.setState({
|
|
sInputValue: val,
|
|
sOpen: true,
|
|
}, this.forcePopupAlign)
|
|
if (props.treeCheckable && !val) {
|
|
this.setState({
|
|
sValue: this.getValue(props, [...this.sValue], false),
|
|
})
|
|
}
|
|
this.__emit('search', val)
|
|
},
|
|
|
|
onDropdownVisibleChange (open) {
|
|
// selection inside combobox cause click
|
|
if (!open && document.activeElement === this.getInputDOMNode()) {
|
|
// return;
|
|
}
|
|
this.setOpenState(open, undefined, !open)
|
|
},
|
|
|
|
// combobox ignore
|
|
onKeyDown (event) {
|
|
const props = this.$props
|
|
if (props.disabled) {
|
|
return
|
|
}
|
|
const keyCode = event.keyCode
|
|
if (this.sOpen && !this.getInputDOMNode()) {
|
|
this.onInputKeyDown(event)
|
|
} else if (keyCode === KeyCode.ENTER || keyCode === KeyCode.DOWN) {
|
|
this.setOpenState(true)
|
|
event.preventDefault()
|
|
}
|
|
},
|
|
|
|
onInputKeyDown (event) {
|
|
const props = this.$props
|
|
if (props.disabled) {
|
|
return
|
|
}
|
|
const state = this.$data
|
|
const keyCode = event.keyCode
|
|
if (isMultiple(props) && !event.target.value && keyCode === KeyCode.BACKSPACE) {
|
|
const value = state.sValue.concat()
|
|
if (value.length) {
|
|
const popValue = value.pop()
|
|
this.removeSelected(this.isLabelInValue() ? popValue : popValue.value)
|
|
}
|
|
return
|
|
}
|
|
if (keyCode === KeyCode.DOWN) {
|
|
if (!state.sOpen) {
|
|
this.openIfHasChildren()
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
return
|
|
}
|
|
} else if (keyCode === KeyCode.ESC) {
|
|
if (state.sOpen) {
|
|
this.setOpenState(false)
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
}
|
|
return
|
|
}
|
|
},
|
|
|
|
onSelect (selectedKeys, info) {
|
|
const item = info.node
|
|
let value = this.sValue
|
|
const props = this.$props
|
|
const selectedValue = getValuePropValue(item)
|
|
const selectedLabel = this.getLabelFromNode(item)
|
|
const checkableSelect = props.treeCheckable && info.event === 'select'
|
|
let event = selectedValue
|
|
if (this.isLabelInValue()) {
|
|
event = {
|
|
value: event,
|
|
label: selectedLabel,
|
|
}
|
|
}
|
|
if (info.selected === false) {
|
|
this.onDeselect(info)
|
|
if (!checkableSelect) return
|
|
}
|
|
this.__emit('select', event, item, info)
|
|
|
|
const checkEvt = info.event === 'check'
|
|
if (isMultiple(props)) {
|
|
this.$nextTick(() => { // clearSearchInput will change sInputValue
|
|
this.clearSearchInput()
|
|
})
|
|
if (checkEvt) {
|
|
value = this.getCheckedNodes(info, props).map(n => {
|
|
return {
|
|
value: getValuePropValue(n),
|
|
label: this.getLabelFromNode(n),
|
|
}
|
|
})
|
|
} else {
|
|
if (value.some(i => i.value === selectedValue)) {
|
|
return
|
|
}
|
|
value = value.concat([{
|
|
value: selectedValue,
|
|
label: selectedLabel,
|
|
}])
|
|
}
|
|
} else {
|
|
if (value.length && value[0].value === selectedValue) {
|
|
this.setOpenState(false)
|
|
return
|
|
}
|
|
value = [{
|
|
value: selectedValue,
|
|
label: selectedLabel,
|
|
}]
|
|
this.setOpenState(false)
|
|
}
|
|
|
|
const extraInfo = {
|
|
triggerValue: selectedValue,
|
|
triggerNode: item,
|
|
}
|
|
if (checkEvt) {
|
|
extraInfo.checked = info.checked
|
|
// if inputValue existing, tree is checkStrictly
|
|
extraInfo.allCheckedNodes = props.treeCheckStrictly || this.sInputValue
|
|
? info.checkedNodes : flatToHierarchy(info.checkedNodesPositions)
|
|
this._checkedNodes = info.checkedNodesPositions
|
|
const _tree = this.getPopupComponentRefs()
|
|
this._treeNodesStates = _tree.checkKeys
|
|
} else {
|
|
extraInfo.selected = info.selected
|
|
}
|
|
|
|
this.fireChange(value, extraInfo)
|
|
if (props.inputValue === null) {
|
|
this.setState({
|
|
sInputValue: '',
|
|
})
|
|
}
|
|
},
|
|
|
|
onDeselect (info) {
|
|
this.removeSelected(getValuePropValue(info.node))
|
|
if (!isMultiple(this.$props)) {
|
|
this.setOpenState(false)
|
|
} else {
|
|
this.clearSearchInput()
|
|
}
|
|
},
|
|
|
|
onPlaceholderClick () {
|
|
this.getInputDOMNode().focus()
|
|
},
|
|
|
|
onClearSelection (event) {
|
|
const props = this.$props
|
|
const state = this.$data
|
|
if (props.disabled) {
|
|
return
|
|
}
|
|
event.stopPropagation()
|
|
this._cacheTreeNodesStates = 'no'
|
|
this._checkedNodes = []
|
|
if (state.sInputValue || state.sValue.length) {
|
|
this.setOpenState(false)
|
|
if (typeof props.inputValue === 'undefined') {
|
|
this.setState({
|
|
sInputValue: '',
|
|
}, () => {
|
|
this.fireChange([])
|
|
})
|
|
} else {
|
|
this.fireChange([])
|
|
}
|
|
}
|
|
},
|
|
|
|
onChoiceAnimationLeave () {
|
|
this.forcePopupAlign()
|
|
},
|
|
|
|
getLabelFromNode (child) {
|
|
return getPropValue(child, this.$props.treeNodeLabelProp)
|
|
},
|
|
|
|
getLabelFromProps (props, value) {
|
|
if (value === undefined) {
|
|
return null
|
|
}
|
|
let label = null
|
|
loopAllChildren(this.renderedTreeData || props.children, item => {
|
|
if (getValuePropValue(item) === value) {
|
|
label = this.getLabelFromNode(item)
|
|
}
|
|
})
|
|
if (label === null) {
|
|
return value
|
|
}
|
|
return label
|
|
},
|
|
|
|
getDropdownContainer () {
|
|
if (!this.dropdownContainer) {
|
|
this.dropdownContainer = document.createElement('div')
|
|
document.body.appendChild(this.dropdownContainer)
|
|
}
|
|
return this.dropdownContainer
|
|
},
|
|
|
|
getSearchPlaceholderElement (hidden) {
|
|
const props = this.$props
|
|
let placeholder
|
|
if (isMultiple(props)) {
|
|
placeholder = getComponentFromProp(this, 'placeholder') || getComponentFromProp(this, 'searchPlaceholder')
|
|
} else {
|
|
placeholder = getComponentFromProp(this, 'searchPlaceholder')
|
|
}
|
|
if (placeholder) {
|
|
return (
|
|
<span
|
|
style={{ display: hidden ? 'none' : 'block' }}
|
|
onClick={this.onPlaceholderClick}
|
|
class={`${props.prefixCls}-search__field__placeholder`}
|
|
>
|
|
{placeholder}
|
|
</span>
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
|
|
getInputElement () {
|
|
const { sInputValue } = this.$data
|
|
const { prefixCls, disabled } = this.$props
|
|
const multiple = isMultiple(this.$props)
|
|
const inputListeners = {
|
|
input: this.onInputChange,
|
|
keydown: this.onInputKeyDown,
|
|
}
|
|
if (multiple) {
|
|
inputListeners.blur = this.onBlur
|
|
inputListeners.focus = this.onFocus
|
|
}
|
|
return (
|
|
<span class={`${prefixCls}-search__field__wrap`}>
|
|
<input
|
|
ref='inputInstance'
|
|
{...{ on: inputListeners }}
|
|
value={sInputValue}
|
|
disabled={disabled}
|
|
class={`${prefixCls}-search__field`}
|
|
role='textbox'
|
|
/>
|
|
<span
|
|
ref='inputMirrorInstance'
|
|
class={`${prefixCls}-search__field__mirror`}
|
|
>
|
|
{sInputValue}
|
|
</span>
|
|
{isMultiple(this.$props) ? null : this.getSearchPlaceholderElement(!!sInputValue)}
|
|
</span>
|
|
)
|
|
},
|
|
|
|
getInputDOMNode () {
|
|
return this.$refs.inputInstance
|
|
},
|
|
|
|
getPopupDOMNode () {
|
|
return this.$refs.trigger.getPopupDOMNode()
|
|
},
|
|
|
|
getPopupComponentRefs () {
|
|
return this.$refs.trigger.getPopupEleRefs()
|
|
},
|
|
|
|
getValue (_props, val, init = true) {
|
|
let value = val
|
|
// if inputValue existing, tree is checkStrictly
|
|
const _strict = init === '__strict' ||
|
|
init && (this.sInputValue ||
|
|
this.inputValue !== _props.inputValue)
|
|
if (_props.treeCheckable &&
|
|
(_props.treeCheckStrictly || _strict)) {
|
|
this.halfCheckedValues = []
|
|
value = []
|
|
val.forEach(i => {
|
|
if (!i.halfChecked) {
|
|
value.push(i)
|
|
} else {
|
|
this.halfCheckedValues.push(i)
|
|
}
|
|
})
|
|
}
|
|
// if (!(_props.treeCheckable && !_props.treeCheckStrictly)) {
|
|
if (!_props.treeCheckable || _props.treeCheckable &&
|
|
(_props.treeCheckStrictly || _strict)) {
|
|
return value
|
|
}
|
|
let checkedTreeNodes
|
|
if (this._cachetreeData && this._cacheTreeNodesStates && this._checkedNodes &&
|
|
!this.sInputValue) {
|
|
this.checkedTreeNodes = checkedTreeNodes = this._checkedNodes
|
|
} else {
|
|
/**
|
|
* Note: `this._treeNodesStates`'s treeNodesStates must correspond to nodes of the
|
|
* final tree (`processTreeNode` function from SelectTrigger.jsx produce the final tree).
|
|
*
|
|
* And, `this._treeNodesStates` from `onSelect` is previous value,
|
|
* so it perhaps only have a few nodes, but the newly filtered tree can have many nodes,
|
|
* thus, you cannot use previous _treeNodesStates.
|
|
*/
|
|
// getTreeNodesStates is not effective.
|
|
this._treeNodesStates = getTreeNodesStates(
|
|
this.renderedTreeData || _props.children,
|
|
value.map(item => item.value)
|
|
)
|
|
this.checkedTreeNodes = checkedTreeNodes = this._treeNodesStates.checkedNodes
|
|
}
|
|
const mapLabVal = arr => arr.map(itemObj => {
|
|
return {
|
|
value: getValuePropValue(itemObj.node),
|
|
label: getPropValue(itemObj.node, _props.treeNodeLabelProp),
|
|
}
|
|
})
|
|
const props = this.$props
|
|
let checkedValues = []
|
|
if (props.showCheckedStrategy === SHOW_ALL) {
|
|
checkedValues = mapLabVal(checkedTreeNodes)
|
|
} else if (props.showCheckedStrategy === SHOW_PARENT) {
|
|
const posArr = filterParentPosition(checkedTreeNodes.map(itemObj => itemObj.pos))
|
|
checkedValues = mapLabVal(checkedTreeNodes.filter(
|
|
itemObj => posArr.indexOf(itemObj.pos) !== -1
|
|
))
|
|
} else {
|
|
checkedValues = mapLabVal(checkedTreeNodes.filter(itemObj => {
|
|
return !itemObj.node.componentOptions.children
|
|
}))
|
|
}
|
|
return checkedValues
|
|
},
|
|
|
|
getCheckedNodes (info, props) {
|
|
// TODO treeCheckable does not support tags/dynamic
|
|
let { checkedNodes } = info
|
|
// if inputValue existing, tree is checkStrictly
|
|
if (props.treeCheckStrictly || this.sInputValue) {
|
|
return checkedNodes
|
|
}
|
|
const checkedNodesPositions = info.checkedNodesPositions
|
|
if (props.showCheckedStrategy === SHOW_ALL) {
|
|
// checkedNodes = checkedNodes
|
|
} else if (props.showCheckedStrategy === SHOW_PARENT) {
|
|
const posArr = filterParentPosition(checkedNodesPositions.map(itemObj => itemObj.pos))
|
|
checkedNodes = checkedNodesPositions.filter(itemObj => posArr.indexOf(itemObj.pos) !== -1)
|
|
.map(itemObj => itemObj.node)
|
|
} else {
|
|
checkedNodes = checkedNodes.filter(n => {
|
|
return !n.componentOptions.children
|
|
})
|
|
}
|
|
return checkedNodes
|
|
},
|
|
|
|
getDeselectedValue (selectedValue) {
|
|
const checkedTreeNodes = this.checkedTreeNodes
|
|
let unCheckPos
|
|
checkedTreeNodes.forEach(itemObj => {
|
|
const nodeProps = getAllProps(itemObj.node)
|
|
if (nodeProps.value === selectedValue) {
|
|
unCheckPos = itemObj.pos
|
|
}
|
|
})
|
|
const newVals = []
|
|
const newCkTns = []
|
|
checkedTreeNodes.forEach(itemObj => {
|
|
if (isPositionPrefix(itemObj.pos, unCheckPos) || isPositionPrefix(unCheckPos, itemObj.pos)) {
|
|
// Filter ancestral and children nodes when uncheck a node.
|
|
return
|
|
}
|
|
const nodeProps = getAllProps(itemObj.node)
|
|
newCkTns.push(itemObj)
|
|
newVals.push(nodeProps.value)
|
|
})
|
|
this.checkedTreeNodes = this._checkedNodes = newCkTns
|
|
const nv = this.sValue.filter(val => newVals.indexOf(val.value) !== -1)
|
|
this.fireChange(nv, { triggerValue: selectedValue, clear: true })
|
|
},
|
|
|
|
setOpenState (open, needFocus, documentClickClose = false) {
|
|
this.clearDelayTimer()
|
|
const { $props: props } = this
|
|
// can not optimize, if children is empty
|
|
// if (this.sOpen === open) {
|
|
// return;
|
|
// }
|
|
if (!this.$props.dropdownVisibleChange(open, { documentClickClose })) {
|
|
return
|
|
}
|
|
this.setState({
|
|
sOpen: open,
|
|
}, () => {
|
|
if (needFocus || open) {
|
|
// Input dom init after first time component render
|
|
// Add delay for this to get focus
|
|
setTimeout(() => {
|
|
if (open || isMultiple(props)) {
|
|
const input = this.getInputDOMNode()
|
|
if (input && document.activeElement !== input) {
|
|
input.focus()
|
|
}
|
|
} else if (this.$refs.selection) {
|
|
this.$refs.selection.focus()
|
|
}
|
|
}, 0)
|
|
}
|
|
})
|
|
},
|
|
|
|
clearSearchInput () {
|
|
this.getInputDOMNode().focus()
|
|
if (!hasProp(this, 'inputValue')) {
|
|
this.setState({ sInputValue: '' })
|
|
}
|
|
},
|
|
|
|
addLabelToValue (props, value_) {
|
|
let value = value_
|
|
if (this.isLabelInValue()) {
|
|
value.forEach((v, i) => {
|
|
if (Object.prototype.toString.call(value[i]) !== '[object Object]') {
|
|
value[i] = {
|
|
value: '',
|
|
label: '',
|
|
}
|
|
return
|
|
}
|
|
v.label = v.label || this.getLabelFromProps(props, v.value)
|
|
})
|
|
} else {
|
|
value = value.map(v => {
|
|
return {
|
|
value: v,
|
|
label: this.getLabelFromProps(props, v),
|
|
}
|
|
})
|
|
}
|
|
return value
|
|
},
|
|
|
|
clearDelayTimer () {
|
|
if (this.delayTimer) {
|
|
clearTimeout(this.delayTimer)
|
|
this.delayTimer = null
|
|
}
|
|
},
|
|
|
|
removeSelected (selectedVal, e) {
|
|
const props = this.$props
|
|
if (props.disabled) {
|
|
return
|
|
}
|
|
|
|
// Do not trigger Trigger popup
|
|
if (e && e.stopPropagation) {
|
|
e.stopPropagation()
|
|
}
|
|
|
|
this._cacheTreeNodesStates = 'no'
|
|
if (props.treeCheckable &&
|
|
(props.showCheckedStrategy === SHOW_ALL || props.showCheckedStrategy === SHOW_PARENT) &&
|
|
!(props.treeCheckStrictly || this.sInputValue)) {
|
|
this.getDeselectedValue(selectedVal)
|
|
return
|
|
}
|
|
// click the node's `x`(in select box), likely trigger the TreeNode's `unCheck` event,
|
|
// cautiously, they are completely different, think about it, the tree may not render at first,
|
|
// but the nodes in select box are ready.
|
|
let label
|
|
const value = this.sValue.filter((singleValue) => {
|
|
if (singleValue.value === selectedVal) {
|
|
label = singleValue.label
|
|
}
|
|
return (singleValue.value !== selectedVal)
|
|
})
|
|
const canMultiple = isMultiple(props)
|
|
|
|
if (canMultiple) {
|
|
let event = selectedVal
|
|
if (this.isLabelInValue()) {
|
|
event = {
|
|
value: selectedVal,
|
|
label,
|
|
}
|
|
}
|
|
this.__emit('deselect', event)
|
|
}
|
|
if (props.treeCheckable) {
|
|
if (this.checkedTreeNodes && this.checkedTreeNodes.length) {
|
|
this.checkedTreeNodes = this._checkedNodes = this.checkedTreeNodes.filter(item => {
|
|
const nodeProps = getAllProps(item.node)
|
|
return value.some(i => i.value === nodeProps.value)
|
|
})
|
|
}
|
|
}
|
|
|
|
this.fireChange(value, { triggerValue: selectedVal, clear: true })
|
|
},
|
|
|
|
openIfHasChildren () {
|
|
const props = this.$props
|
|
if (props.children.length || (props.treeData && props.treeData.length) || !isMultiple(props)) {
|
|
this.setOpenState(true)
|
|
}
|
|
},
|
|
|
|
fireChange (value, extraInfo = {}) {
|
|
const props = getOptionProps(this)
|
|
const vals = value.map(i => i.value)
|
|
const sv = this.sValue.map(i => i.value)
|
|
if (vals.length !== sv.length || !vals.every((val, index) => sv[index] === val)) {
|
|
const ex = {
|
|
preValue: [...this.sValue],
|
|
...extraInfo,
|
|
}
|
|
let labs = null
|
|
let vls = value
|
|
if (!this.isLabelInValue()) {
|
|
labs = value.map(i => i.label)
|
|
vls = vls.map(v => v.value)
|
|
} else if (this.halfCheckedValues && this.halfCheckedValues.length) {
|
|
this.halfCheckedValues.forEach(i => {
|
|
if (!vls.some(v => v.value === i.value)) {
|
|
vls.push(i)
|
|
}
|
|
})
|
|
}
|
|
if (props.treeCheckable && ex.clear) {
|
|
const treeData = this.renderedTreeData || props.children
|
|
ex.allCheckedNodes = flatToHierarchy(filterAllCheckedData(vals, treeData))
|
|
}
|
|
if (props.treeCheckable && this.sInputValue) {
|
|
const _vls = [...this.sValue]
|
|
if (ex.checked) {
|
|
value.forEach(i => {
|
|
if (_vls.every(ii => ii.value !== i.value)) {
|
|
_vls.push({ ...i })
|
|
}
|
|
})
|
|
} else {
|
|
let index
|
|
const includeVal = _vls.some((i, ind) => {
|
|
if (i.value === ex.triggerValue) {
|
|
index = ind
|
|
return true
|
|
}
|
|
})
|
|
if (includeVal) {
|
|
_vls.splice(index, 1)
|
|
}
|
|
}
|
|
vls = _vls
|
|
if (!this.isLabelInValue()) {
|
|
labs = _vls.map(v => v.label)
|
|
vls = _vls.map(v => v.value)
|
|
}
|
|
}
|
|
this._savedValue = isMultiple(props) ? vls : vls[0]
|
|
this.__emit('change', this._savedValue, labs, ex)
|
|
if (!('value' in props)) {
|
|
this._cacheTreeNodesStates = false
|
|
this.setState({
|
|
sValue: this.getValue(props, toArray(this._savedValue).map((v, i) => {
|
|
return this.isLabelInValue() ? v : {
|
|
value: v,
|
|
label: labs && labs[i],
|
|
}
|
|
})),
|
|
}, this.forcePopupAlign)
|
|
}
|
|
}
|
|
},
|
|
|
|
isLabelInValue () {
|
|
const { treeCheckable, treeCheckStrictly, labelInValue } = this.$props
|
|
if (treeCheckable && treeCheckStrictly) {
|
|
return true
|
|
}
|
|
return labelInValue || false
|
|
},
|
|
onFocus (e) {
|
|
this.__emit('focus', e)
|
|
},
|
|
onBlur (e) {
|
|
this.__emit('blur', e)
|
|
},
|
|
|
|
focus () {
|
|
if (!isMultiple(this.$props)) {
|
|
this.$refs.selection.focus()
|
|
} else {
|
|
this.getInputDOMNode().focus()
|
|
}
|
|
},
|
|
|
|
blur () {
|
|
if (!isMultiple(this.$props)) {
|
|
this.$refs.selection.blur()
|
|
} else {
|
|
this.getInputDOMNode().blur()
|
|
}
|
|
},
|
|
|
|
forcePopupAlign () {
|
|
this.$refs.trigger.$refs.trigger.forcePopupAlign()
|
|
},
|
|
|
|
renderTopControlNode () {
|
|
const { sValue: value } = this.$data
|
|
const props = this.$props
|
|
const { choiceTransitionName, prefixCls, maxTagTextLength } = props
|
|
const multiple = isMultiple(props)
|
|
|
|
// single and not combobox, input is inside dropdown
|
|
if (!multiple) {
|
|
let innerNode = (<span
|
|
key='placeholder'
|
|
class={`${prefixCls}-selection__placeholder`}
|
|
>
|
|
{getComponentFromProp(this, 'placeholder') || ''}
|
|
</span>)
|
|
if (value.length) {
|
|
innerNode = (<span
|
|
key='value'
|
|
title={toTitle(value[0].label)}
|
|
class={`${prefixCls}-selection-selected-value`}
|
|
>
|
|
{value[0].label}
|
|
</span>)
|
|
}
|
|
return (<span class={`${prefixCls}-selection__rendered`}>
|
|
{innerNode}
|
|
</span>)
|
|
}
|
|
|
|
const selectedValueNodes = value.map((singleValue) => {
|
|
let content = singleValue.label
|
|
const title = content
|
|
if (maxTagTextLength && typeof content === 'string' && content.length > maxTagTextLength) {
|
|
content = `${content.slice(0, maxTagTextLength)}...`
|
|
}
|
|
return (
|
|
<li
|
|
style={UNSELECTABLE_STYLE}
|
|
onMousedown={preventDefaultEvent}
|
|
class={`${prefixCls}-selection__choice`}
|
|
key={singleValue.value}
|
|
title={toTitle(title)}
|
|
{...{ attrs: UNSELECTABLE_ATTRIBUTE }}
|
|
>
|
|
<span
|
|
class={`${prefixCls}-selection__choice__remove`}
|
|
onClick={(event) => {
|
|
this.removeSelected(singleValue.value, event)
|
|
}}
|
|
/>
|
|
<span class={`${prefixCls}-selection__choice__content`}>{content}</span>
|
|
</li>
|
|
)
|
|
})
|
|
|
|
selectedValueNodes.push(<li
|
|
class={`${prefixCls}-search ${prefixCls}-search--inline`}
|
|
key='__input'
|
|
>
|
|
{this.getInputElement()}
|
|
</li>)
|
|
const className = `${prefixCls}-selection__rendered`
|
|
if (choiceTransitionName) {
|
|
const transitionProps = getTransitionProps(choiceTransitionName, {
|
|
tag: 'ul',
|
|
afterLeave: this.onChoiceAnimationLeave,
|
|
})
|
|
return (<transition-group
|
|
class={className}
|
|
{...transitionProps}
|
|
>
|
|
{selectedValueNodes}
|
|
</transition-group>)
|
|
}
|
|
return (<ul class={className}>{selectedValueNodes}</ul>)
|
|
},
|
|
|
|
renderTreeData (props) {
|
|
const validProps = props || this.$props
|
|
if (validProps.treeData) {
|
|
if (props && props.treeData === this.preProps.treeData && this.renderedTreeData) {
|
|
// cache and use pre data.
|
|
this._cachetreeData = true
|
|
return this.renderedTreeData
|
|
}
|
|
this._cachetreeData = false
|
|
let treeData = [...validProps.treeData]
|
|
// process treeDataSimpleMode
|
|
if (validProps.treeDataSimpleMode) {
|
|
let simpleFormat = {
|
|
id: 'id',
|
|
pId: 'pId',
|
|
rootPId: null,
|
|
}
|
|
if (Object.prototype.toString.call(validProps.treeDataSimpleMode) === '[object Object]') {
|
|
simpleFormat = { ...simpleFormat, ...validProps.treeDataSimpleMode }
|
|
}
|
|
treeData = processSimpleTreeData(treeData, simpleFormat)
|
|
}
|
|
return this.loopTreeData(treeData, undefined, this.preProps.treeCheckable)
|
|
}
|
|
},
|
|
},
|
|
|
|
render () {
|
|
const props = this.$props
|
|
const multiple = isMultiple(props)
|
|
const state = this.$data
|
|
const { disabled, allowClear, prefixCls } = props
|
|
const ctrlNode = this.renderTopControlNode()
|
|
let extraSelectionProps = {}
|
|
if (!multiple) {
|
|
extraSelectionProps = {
|
|
on: {
|
|
keydown: this.onKeyDown,
|
|
blur: this.onBlur,
|
|
focus: this.onFocus,
|
|
},
|
|
attrs: {
|
|
tabIndex: 0,
|
|
},
|
|
}
|
|
}
|
|
const rootCls = {
|
|
[prefixCls]: 1,
|
|
[`${prefixCls}-open`]: state.sOpen,
|
|
[`${prefixCls}-focused`]: state.sOpen || state.sFocused,
|
|
// [`${prefixCls}-combobox`]: isCombobox(props),
|
|
[`${prefixCls}-disabled`]: disabled,
|
|
[`${prefixCls}-enabled`]: !disabled,
|
|
[`${prefixCls}-allow-clear`]: !!props.allowClear,
|
|
}
|
|
|
|
const clear = (<span
|
|
key='clear'
|
|
class={`${prefixCls}-selection__clear`}
|
|
onClick={this.onClearSelection}
|
|
/>)
|
|
const selectTriggerProps = {
|
|
props: {
|
|
...props,
|
|
treeNodes: props.children,
|
|
treeData: this.renderedTreeData,
|
|
_cachetreeData: this._cachetreeData,
|
|
_treeNodesStates: this._treeNodesStates,
|
|
halfCheckedValues: this.halfCheckedValues,
|
|
multiple: multiple,
|
|
disabled: disabled,
|
|
visible: state.sOpen,
|
|
inputValue: state.sInputValue,
|
|
inputElement: this.getInputElement(),
|
|
value: state.sValue,
|
|
dropdownVisibleChange: this.onDropdownVisibleChange,
|
|
getPopupContainer: props.getPopupContainer,
|
|
filterTreeNode: this.filterTreeNode === undefined ? filterFn : this.filterTreeNode,
|
|
},
|
|
on: {
|
|
...this.$listeners,
|
|
select: this.onSelect,
|
|
},
|
|
ref: 'trigger',
|
|
}
|
|
return (
|
|
<SelectTrigger {...selectTriggerProps}>
|
|
<span
|
|
onClick={props.onClick}
|
|
class={classnames(rootCls)}
|
|
>
|
|
<span
|
|
ref='selection'
|
|
key='selection'
|
|
class={`${prefixCls}-selection
|
|
${prefixCls}-selection--${multiple ? 'multiple' : 'single'}`}
|
|
role='combobox'
|
|
aria-autocomplete='list'
|
|
aria-haspopup='true'
|
|
aria-expanded={state.sOpen}
|
|
{...extraSelectionProps}
|
|
>
|
|
{ctrlNode}
|
|
{allowClear && state.sValue.length &&
|
|
state.sValue[0].value ? clear : null}
|
|
{multiple || !props.showArrow ? null
|
|
: (<span
|
|
key='arrow'
|
|
class={`${prefixCls}-arrow`}
|
|
style={{ outline: 'none' }}
|
|
>
|
|
<b/>
|
|
</span>)}
|
|
{multiple
|
|
? this.getSearchPlaceholderElement(!!state.sInputValue || state.sValue.length)
|
|
: null}
|
|
</span>
|
|
</span>
|
|
</SelectTrigger>
|
|
)
|
|
},
|
|
}
|
|
|
|
Select.SHOW_ALL = SHOW_ALL
|
|
Select.SHOW_PARENT = SHOW_PARENT
|
|
Select.SHOW_CHILD = SHOW_CHILD
|
|
|
|
export default Select
|