Browse Source

add vc-select

pull/9/head
tangjinzhou 7 years ago
parent
commit
62adeccd94
  1. 186
      components/vc-select/DropdownMenu.vue
  2. 5
      components/vc-select/OptGroup.vue
  3. 13
      components/vc-select/Option.vue
  4. 84
      components/vc-select/PropTypes.js
  5. 0
      components/vc-select/Select.vue
  6. 0
      components/vc-select/SelectTrigger.vue
  7. 165
      components/vc-select/util.js

186
components/vc-select/DropdownMenu.vue

@ -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>

5
components/vc-select/OptGroup.vue

@ -0,0 +1,5 @@
<script>
export default {
isSelectOptGroup: true,
}
</script>

13
components/vc-select/Option.vue

@ -0,0 +1,13 @@
<script>
import PropTypes from '../_util/vue-types'
export default {
props: {
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
},
isSelectOption: true,
}
</script>

84
components/vc-select/PropTypes.js

@ -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
components/vc-select/Select.vue

0
components/vc-select/SelectTrigger.vue

165
components/vc-select/util.js

@ -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…
Cancel
Save