tangjinzhou
7 years ago
7 changed files with 453 additions and 0 deletions
@ -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