add vc-select
parent
c258047c98
commit
62adeccd94
|
@ -0,0 +1,186 @@
|
|||
<script>
|
||||
import PropTypes from '../_util/vue-types'
|
||||
import toArray from 'rc-util/lib/Children/toArray'
|
||||
import Menu from '../vc-menu'
|
||||
import scrollIntoView from 'dom-scroll-into-view'
|
||||
import { getSelectKeys, preventDefaultEvent, saveRef } from './util'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
defaultActiveFirstOption: PropTypes.bool,
|
||||
value: PropTypes.any,
|
||||
dropdownMenuStyle: PropTypes.object,
|
||||
multiple: PropTypes.bool,
|
||||
onPopupFocus: PropTypes.func,
|
||||
onPopupScroll: PropTypes.func,
|
||||
onMenuDeSelect: PropTypes.func,
|
||||
onMenuSelect: PropTypes.func,
|
||||
prefixCls: PropTypes.string,
|
||||
menuItems: PropTypes.any,
|
||||
inputValue: PropTypes.string,
|
||||
visible: PropTypes.bool,
|
||||
},
|
||||
|
||||
beforeMount () {
|
||||
this.lastInputValue = this.$props.inputValue
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.scrollActiveItemToView()
|
||||
})
|
||||
this.lastVisible = this.$props.visible
|
||||
},
|
||||
watch: {
|
||||
visible (val) {
|
||||
if (!val) {
|
||||
this.lastVisible = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// shouldComponentUpdate (nextProps) {
|
||||
// if (!nextProps.visible) {
|
||||
// this.lastVisible = false
|
||||
// }
|
||||
// // freeze when hide
|
||||
// return nextProps.visible
|
||||
// }
|
||||
|
||||
updated () {
|
||||
const prevProps = this.prevProps
|
||||
const props = this.$props
|
||||
if (!prevProps.visible && props.visible) {
|
||||
this.$nextTick(() => {
|
||||
this.scrollActiveItemToView()
|
||||
})
|
||||
}
|
||||
this.lastVisible = props.visible
|
||||
this.lastInputValue = props.inputValue
|
||||
},
|
||||
methods: {
|
||||
scrollActiveItemToView () {
|
||||
// scroll into view
|
||||
const itemComponent = this.$refs.firstActiveItem.$el
|
||||
const props = this.$props
|
||||
|
||||
if (itemComponent) {
|
||||
const scrollIntoViewOpts = {
|
||||
onlyScrollIfNeeded: true,
|
||||
}
|
||||
if (
|
||||
(!props.value || props.value.length === 0) &&
|
||||
props.firstActiveValue
|
||||
) {
|
||||
scrollIntoViewOpts.alignWithTop = true
|
||||
}
|
||||
|
||||
scrollIntoView(
|
||||
itemComponent,
|
||||
this.$refs.menuRef.$el,
|
||||
scrollIntoViewOpts
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
renderMenu () {
|
||||
const props = this.props
|
||||
const {
|
||||
menuItems,
|
||||
defaultActiveFirstOption,
|
||||
value,
|
||||
prefixCls,
|
||||
multiple,
|
||||
onMenuSelect,
|
||||
inputValue,
|
||||
firstActiveValue,
|
||||
} = props
|
||||
if (menuItems && menuItems.length) {
|
||||
const menuProps = {}
|
||||
if (multiple) {
|
||||
menuProps.onDeselect = props.onMenuDeselect
|
||||
menuProps.onSelect = onMenuSelect
|
||||
} else {
|
||||
menuProps.onClick = onMenuSelect
|
||||
}
|
||||
|
||||
const selectedKeys = getSelectKeys(menuItems, value)
|
||||
const activeKeyProps = {}
|
||||
|
||||
let clonedMenuItems = menuItems
|
||||
if (selectedKeys.length || firstActiveValue) {
|
||||
if (props.visible && !this.lastVisible) {
|
||||
activeKeyProps.activeKey = selectedKeys[0] || firstActiveValue
|
||||
}
|
||||
let foundFirst = false
|
||||
// set firstActiveItem via cloning menus
|
||||
// for scroll into view
|
||||
const clone = item => {
|
||||
if (
|
||||
(!foundFirst && selectedKeys.indexOf(item.key) !== -1) ||
|
||||
(!foundFirst &&
|
||||
!selectedKeys.length &&
|
||||
firstActiveValue.indexOf(item.key) !== -1)
|
||||
) {
|
||||
foundFirst = true
|
||||
return cloneElement(item, {
|
||||
ref: ref => {
|
||||
this.firstActiveItem = ref
|
||||
},
|
||||
})
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
clonedMenuItems = menuItems.map(item => {
|
||||
if (item.type.isMenuItemGroup) {
|
||||
const children = toArray(item.props.children).map(clone)
|
||||
return cloneElement(item, {}, children)
|
||||
}
|
||||
return clone(item)
|
||||
})
|
||||
}
|
||||
|
||||
// clear activeKey when inputValue change
|
||||
const lastValue = value && value[value.length - 1]
|
||||
if (inputValue !== this.lastInputValue && (!lastValue || !lastValue.backfill)) {
|
||||
activeKeyProps.activeKey = ''
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu
|
||||
ref={saveRef(this, 'menuRef')}
|
||||
style={this.props.dropdownMenuStyle}
|
||||
defaultActiveFirst={defaultActiveFirstOption}
|
||||
{...activeKeyProps}
|
||||
multiple={multiple}
|
||||
focusable={false}
|
||||
{...menuProps}
|
||||
selectedKeys={selectedKeys}
|
||||
prefixCls={`${prefixCls}-menu`}
|
||||
>
|
||||
{clonedMenuItems}
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
render () {
|
||||
const renderMenu = this.renderMenu()
|
||||
return renderMenu ? (
|
||||
<div
|
||||
style={{ overflow: 'auto' }}
|
||||
onFocus={this.props.onPopupFocus}
|
||||
onMouseDown={preventDefaultEvent}
|
||||
onScroll={this.props.onPopupScroll}
|
||||
>
|
||||
{renderMenu}
|
||||
</div>
|
||||
) : null
|
||||
},
|
||||
}
|
||||
|
||||
DropdownMenu.displayName = 'DropdownMenu'
|
||||
|
||||
</script>
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
export default {
|
||||
isSelectOptGroup: true,
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import PropTypes from '../_util/vue-types'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
},
|
||||
isSelectOption: true,
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,84 @@
|
|||
import PropTypes from '../_util/vue-types'
|
||||
|
||||
function valueType (props, propName, componentName) {
|
||||
const basicType = PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
])
|
||||
|
||||
const labelInValueShape = PropTypes.shape({
|
||||
key: basicType.isRequired,
|
||||
label: PropTypes.node,
|
||||
})
|
||||
if (props.labelInValue) {
|
||||
const validate = PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(labelInValueShape),
|
||||
labelInValueShape,
|
||||
])
|
||||
const error = validate(...arguments)
|
||||
if (error) {
|
||||
return new Error(
|
||||
`Invalid prop \`${propName}\` supplied to \`${componentName}\`, ` +
|
||||
`when you set \`labelInValue\` to \`true\`, \`${propName}\` should in ` +
|
||||
`shape of \`{ key: string | number, label?: ReactNode }\`.`
|
||||
)
|
||||
}
|
||||
} else if (
|
||||
(props.mode === 'multiple' || props.mode === 'tags' || props.multiple || props.tags) &&
|
||||
props[propName] === ''
|
||||
) {
|
||||
return new Error(
|
||||
`Invalid prop \`${propName}\` of type \`string\` supplied to \`${componentName}\`, ` +
|
||||
`expected \`array\` when \`multiple\` or \`tags\` is \`true\`.`
|
||||
)
|
||||
} else {
|
||||
const validate = PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(basicType),
|
||||
basicType,
|
||||
])
|
||||
return validate(...arguments)
|
||||
}
|
||||
}
|
||||
|
||||
export const SelectPropTypes = {
|
||||
defaultActiveFirstOption: PropTypes.bool,
|
||||
multiple: PropTypes.bool,
|
||||
filterOption: PropTypes.any,
|
||||
// children: PropTypes.any,
|
||||
showSearch: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
allowClear: PropTypes.bool,
|
||||
showArrow: PropTypes.bool,
|
||||
tags: PropTypes.bool,
|
||||
prefixCls: PropTypes.string,
|
||||
// className: PropTypes.string,
|
||||
transitionName: PropTypes.string,
|
||||
optionLabelProp: PropTypes.string,
|
||||
optionFilterProp: PropTypes.string,
|
||||
animation: PropTypes.string,
|
||||
choiceTransitionName: PropTypes.string,
|
||||
// onChange: PropTypes.func,
|
||||
// onBlur: PropTypes.func,
|
||||
// onFocus: PropTypes.func,
|
||||
// onSelect: PropTypes.func,
|
||||
// onSearch: PropTypes.func,
|
||||
// onPopupScroll: PropTypes.func,
|
||||
// onMouseEnter: PropTypes.func,
|
||||
// onMouseLeave: PropTypes.func,
|
||||
// onInputKeyDown: PropTypes.func,
|
||||
placeholder: PropTypes.any,
|
||||
// onDeselect: PropTypes.func,
|
||||
labelInValue: PropTypes.bool,
|
||||
value: valueType,
|
||||
defaultValue: valueType,
|
||||
dropdownStyle: PropTypes.object,
|
||||
maxTagTextLength: PropTypes.number,
|
||||
maxTagCount: PropTypes.number,
|
||||
maxTagPlaceholder: PropTypes.oneOfType([
|
||||
PropTypes.node,
|
||||
PropTypes.func,
|
||||
]),
|
||||
tokenSeparators: PropTypes.arrayOf(PropTypes.string),
|
||||
getInputElement: PropTypes.func,
|
||||
showAction: PropTypes.arrayOf(PropTypes.string),
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
export function getValuePropValue (child) {
|
||||
const props = child.props
|
||||
if ('value' in props) {
|
||||
return props.value
|
||||
}
|
||||
if (child.key) {
|
||||
return child.key
|
||||
}
|
||||
if (child.type && child.type.isSelectOptGroup && props.label) {
|
||||
return props.label
|
||||
}
|
||||
throw new Error(
|
||||
`Need at least a key or a value or a label (only for OptGroup) for ${child}`
|
||||
)
|
||||
}
|
||||
|
||||
export function getPropValue (child, prop) {
|
||||
if (prop === 'value') {
|
||||
return getValuePropValue(child)
|
||||
}
|
||||
return child.props[prop]
|
||||
}
|
||||
|
||||
export function isMultiple (props) {
|
||||
return props.multiple
|
||||
}
|
||||
|
||||
export function isCombobox (props) {
|
||||
return props.combobox
|
||||
}
|
||||
|
||||
export function isMultipleOrTags (props) {
|
||||
return props.multiple || props.tags
|
||||
}
|
||||
|
||||
export function isMultipleOrTagsOrCombobox (props) {
|
||||
return isMultipleOrTags(props) || isCombobox(props)
|
||||
}
|
||||
|
||||
export function isSingleMode (props) {
|
||||
return !isMultipleOrTagsOrCombobox(props)
|
||||
}
|
||||
|
||||
export function toArray (value) {
|
||||
let ret = value
|
||||
if (value === undefined) {
|
||||
ret = []
|
||||
} else if (!Array.isArray(value)) {
|
||||
ret = [value]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
export function preventDefaultEvent (e) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
export function findIndexInValueByKey (value, key) {
|
||||
let index = -1
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (value[i].key === key) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
export function findIndexInValueByLabel (value, label) {
|
||||
let index = -1
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (toArray(value[i].label).join('') === label) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
export function getSelectKeys (menuItems, value) {
|
||||
if (value === null || value === undefined) {
|
||||
return []
|
||||
}
|
||||
let selectedKeys = []
|
||||
menuItems.forEach(item => {
|
||||
if (item.type.isMenuItemGroup) {
|
||||
selectedKeys = selectedKeys.concat(
|
||||
getSelectKeys(item.props.children, value)
|
||||
)
|
||||
} else {
|
||||
const itemValue = getValuePropValue(item)
|
||||
const itemKey = item.key
|
||||
if (findIndexInValueByKey(value, itemValue) !== -1 && itemKey) {
|
||||
selectedKeys.push(itemKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
return selectedKeys
|
||||
}
|
||||
|
||||
export const UNSELECTABLE_STYLE = {
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
}
|
||||
|
||||
export const UNSELECTABLE_ATTRIBUTE = {
|
||||
unselectable: 'unselectable',
|
||||
}
|
||||
|
||||
export function findFirstMenuItem (children) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
if (child.type.isMenuItemGroup) {
|
||||
const found = findFirstMenuItem(child.props.children)
|
||||
if (found) {
|
||||
return found
|
||||
}
|
||||
} else if (!child.props.disabled) {
|
||||
return child
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function includesSeparators (string, separators) {
|
||||
for (let i = 0; i < separators.length; ++i) {
|
||||
if (string.lastIndexOf(separators[i]) > 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function splitBySeparators (string, separators) {
|
||||
const reg = new RegExp(`[${separators.join()}]`)
|
||||
return string.split(reg).filter(token => token)
|
||||
}
|
||||
|
||||
export function defaultFilterFn (input, child) {
|
||||
if (child.props.disabled) {
|
||||
return false
|
||||
}
|
||||
const value = String(getPropValue(child, this.props.optionFilterProp))
|
||||
return (
|
||||
value.toLowerCase().indexOf(input.toLowerCase()) > -1
|
||||
)
|
||||
}
|
||||
|
||||
export function validateOptionValue (value, props) {
|
||||
if (isSingleMode(props) || isMultiple(props)) {
|
||||
return
|
||||
}
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error(
|
||||
`Invalid \`value\` of type \`${typeof value}\` supplied to Option, ` +
|
||||
`expected \`string\` when \`tags/combobox\` is \`true\`.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function saveRef (instance, name) {
|
||||
return (node) => {
|
||||
instance[name] = node
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue