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