From 4ecbecab414578bf5d9aa714affd9fee25fe761b Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Mon, 26 Feb 2018 18:44:06 +0800 Subject: [PATCH] update select demo --- components/_util/vue-types/index.js | 4 +- components/select/demo/index.vue | 56 +++++++++ components/select/demo/search-box.md | 1 - components/select/demo/search.md | 48 +++++++ components/select/demo/select-users.md | 76 +++++++++++ components/select/demo/size.md | 86 +++++++++++++ components/select/demo/tags.md | 37 ++++++ components/select/index.en-US.md | 76 +++++++++++ components/select/index.vue | 7 +- components/select/index.zh-CN.md | 76 +++++++++++ components/vc-select/DropdownMenu.vue | 26 ++-- components/vc-select/Select.vue | 166 +++++++++++++------------ components/vc-select/SelectTrigger.vue | 11 +- 13 files changed, 573 insertions(+), 97 deletions(-) create mode 100644 components/select/demo/index.vue create mode 100644 components/select/demo/search.md create mode 100644 components/select/demo/select-users.md create mode 100644 components/select/demo/size.md create mode 100644 components/select/demo/tags.md create mode 100644 components/select/index.en-US.md create mode 100644 components/select/index.zh-CN.md diff --git a/components/_util/vue-types/index.js b/components/_util/vue-types/index.js index 05ecc25a8..5d2a8e5f6 100644 --- a/components/_util/vue-types/index.js +++ b/components/_util/vue-types/index.js @@ -134,7 +134,7 @@ const VuePropTypes = { // delegate to Vue native prop check return toType('oneOfType', { type: nativeChecks, - }) + }).def(undefined) } const typesStr = arr.map((type) => { @@ -153,7 +153,7 @@ const VuePropTypes = { }) if (!valid) warn(`oneOfType - value type should be one of "${typesStr}"`) return valid - }) + }).def(undefined) }, arrayOf (type) { diff --git a/components/select/demo/index.vue b/components/select/demo/index.vue new file mode 100644 index 000000000..57d1e722a --- /dev/null +++ b/components/select/demo/index.vue @@ -0,0 +1,56 @@ + diff --git a/components/select/demo/search-box.md b/components/select/demo/search-box.md index 07e96f0ed..1d5949aa4 100644 --- a/components/select/demo/search-box.md +++ b/components/select/demo/search-box.md @@ -1,7 +1,6 @@ #### 搜索框 -省市联动是典型的例子。 自动补全和远程数据结合。 diff --git a/components/select/demo/search.md b/components/select/demo/search.md new file mode 100644 index 000000000..6912e8c14 --- /dev/null +++ b/components/select/demo/search.md @@ -0,0 +1,48 @@ + + +#### 带搜索框 +展开后可对选项进行搜索。 + + + +#### Select with search field +Search the options while expanded. + + +```html + + +``` + diff --git a/components/select/demo/select-users.md b/components/select/demo/select-users.md new file mode 100644 index 000000000..fcb3340ed --- /dev/null +++ b/components/select/demo/select-users.md @@ -0,0 +1,76 @@ + + +#### 搜索用户 +一个带有远程搜索,节流控制,请求时序控制,加载状态的多选示例。 + + + +#### Search and Select Users +A complete multiple select sample with remote search, debounce fetch, ajax callback order flow, and loading state. + + +```html + + +``` + diff --git a/components/select/demo/size.md b/components/select/demo/size.md new file mode 100644 index 000000000..3a0b30aa6 --- /dev/null +++ b/components/select/demo/size.md @@ -0,0 +1,86 @@ + + +#### 三种大小 +三种大小的选择框,当 size 分别为 `large` 和 `small` 时,输入框高度为 `40px` 和 `24px` ,默认高度为 `32px`。 + + + +#### Sizes +The height of the input field for the select defaults to 32px. If size is set to large, the height will be 40px, and if set to small, 24px. + + +```html + + +``` + diff --git a/components/select/demo/tags.md b/components/select/demo/tags.md new file mode 100644 index 000000000..129f6a98b --- /dev/null +++ b/components/select/demo/tags.md @@ -0,0 +1,37 @@ + + +#### 标签 +tags select,随意输入的内容(scroll the menu) + + + +#### Tags +Select with tags, transform input to tag (scroll the menu) + + +```html + + +``` + diff --git a/components/select/index.en-US.md b/components/select/index.en-US.md new file mode 100644 index 000000000..611e60e89 --- /dev/null +++ b/components/select/index.en-US.md @@ -0,0 +1,76 @@ + +## API + +```html + + lucy + +``` + +### Select props + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| allowClear | Show clear button. | boolean | false | +| autoFocus | Get focus by default | boolean | false | +| defaultActiveFirstOption | Whether active first option by default | boolean | true | +| defaultValue | Initial selected option. | string\|number\|string\[]\|number\[] | - | +| disabled | Whether disabled select | boolean | false | +| dropdownClassName | className of dropdown menu | string | - | +| dropdownMatchSelectWidth | Whether dropdown's with is same with select. | boolean | true | +| dropdownStyle | style of dropdown menu | object | - | +| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded. | boolean or function(inputValue, option) | true | +| firstActiveValue | Value of action option by default | string\|string\[] | - | +| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. [Example](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | +| labelInValue | whether to embed label in value, turn the format of value from `string` to `{key: string, label: vNodes}` | boolean | false | +| maxTagCount | Max tag count to show | number | - | +| maxTagPlaceholder | Placeholder for not showing tags | slot/function(omittedValues) | - | +| mode | Set mode of Select | 'multiple' \| 'tags' \| 'combobox' | - | +| notFoundContent | Specify content to show when no result matches.. | string\|slot | 'Not Found' | +| optionFilterProp | Which prop value of option will be used for filter if filterOption is true | string | value | +| optionLabelProp | Which prop value of option will render as content of select. | string | `children` | +| placeholder | Placeholder of select | string\|slot | - | +| showSearch | Whether show search input in single mode. | boolean | false | +| showArrow | Whether to show the drop-down arrow | boolean | true | +| size | Size of Select input. `default` `large` `small` | string | default | +| tokenSeparators | Separator used to tokenize on tag/multiple mode | string\[] | | +| value(v-model) | Current selected option. | string\|number\|string\[]\|number\[] | - | + + +### events +| Events Name | Description | Arguments | +| --- | --- | --- | +| blur | Called when blur | function | +| change | Called when select an option or input value change, or value of input is changed in combobox mode | function(value, option:Option/Array) | +| deselect | Called when a option is deselected, the params are option's value (or key) . only called for multiple or tags, effective in multiple or tags mode only. | function(value, option:Option) | +| focus | Called when focus | function | +| inputKeydown | Called when key pressed | function | +| mouseenter | Called when mouse enter | function | +| mouseleave | Called when mouse leave | function | +| popupScroll | Called when dropdown scrolls | function | +| search | Callback function that is fired when input changed. | function(value: string) | +| select | Called when a option is selected, the params are option's value (or key) and option instance. | function(value, option:Option) | + + +### Select Methods + +| Name | Description | +| ---- | ----------- | +| blur() | Remove focus | +| focus() | Get focus | + +### Option props + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| disabled | Disable this option | boolean | false | +| key | Same usage as `value`. If Vue request you to set this property, you can set it to value of option, and then omit value property. | string | | +| title | `title` of Select after select this Option | string | - | +| value | default to filter with this property | string\|number | - | + +### OptGroup props + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| key | | string | - | +| label | Group label | string\|slot | - | diff --git a/components/select/index.vue b/components/select/index.vue index 9c54cd820..02d9c7dcd 100644 --- a/components/select/index.vue +++ b/components/select/index.vue @@ -3,7 +3,7 @@ import PropTypes from '../_util/vue-types' import VcSelect, { Option, OptGroup } from '../vc-select' import LocaleReceiver from '../locale-provider/LocaleReceiver' import defaultLocale from '../locale-provider/default' -import { getComponentFromProp, getOptionProps } from '../_util/props-util' +import { getComponentFromProp, getOptionProps, filterEmpty } from '../_util/props-util' const AbstractSelectProps = { prefixCls: PropTypes.string, @@ -15,7 +15,7 @@ const AbstractSelectProps = { allowClear: PropTypes.bool, disabled: PropTypes.bool, tabIndex: PropTypes.number, - placeholder: PropTypes.string, + placeholder: PropTypes.any, defaultActiveFirstOption: PropTypes.bool, dropdownClassName: PropTypes.string, dropdownStyle: PropTypes.any, @@ -132,6 +132,7 @@ export default { optionLabelProp: optionLabelProp || 'children', notFoundContent: this.getNotFoundContent(locale), maxTagPlaceholder: getComponentFromProp(this, 'maxTagPlaceholder'), + placeholder: getComponentFromProp(this, 'placeholder'), }, on: this.$listeners, class: cls, @@ -140,7 +141,7 @@ export default { return ( - {this.$slots.default} + {filterEmpty(this.$slots.default)} ) }, diff --git a/components/select/index.zh-CN.md b/components/select/index.zh-CN.md new file mode 100644 index 000000000..8505997c6 --- /dev/null +++ b/components/select/index.zh-CN.md @@ -0,0 +1,76 @@ +## API + +```html + + lucy + +``` + +### Select props + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| allowClear | 支持清除 | boolean | false | +| autoFocus | 默认获取焦点 | boolean | false | +| defaultActiveFirstOption | 是否默认高亮第一个选项。 | boolean | true | +| defaultValue | 指定默认选中的条目 | string\|string\[]\|number\|number\[] | - | +| disabled | 是否禁用 | boolean | false | +| dropdownClassName | 下拉菜单的 className 属性 | string | - | +| dropdownMatchSelectWidth | 下拉菜单和选择器同宽 | boolean | true | +| dropdownStyle | 下拉菜单的 style 属性 | object | - | +| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 `true`,反之则返回 `false`。 | boolean or function(inputValue, option) | true | +| firstActiveValue | 默认高亮的选项 | string\|string\[] | - | +| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | Function(triggerNode) | () => document.body | +| labelInValue | 是否把每个选项的 label 包装到 value 中,会把 Select 的 value 类型从 `string` 变为 `{key: string, label: vNodes}` 的格式 | boolean | false | +| maxTagCount | 最多显示多少个 tag | number | - | +| maxTagPlaceholder | 隐藏 tag 时显示的内容 | slot/function(omittedValues) | - | +| mode | 设置 Select 的模式 | 'multiple' \| 'tags' \| 'combobox' | - | +| notFoundContent | 当下拉列表为空时显示的内容 | string\|slot | 'Not Found' | +| optionFilterProp | 搜索时过滤对应的 option 属性,如设置为 children 表示对内嵌内容进行搜索 | string | value | +| optionLabelProp | 回填到选择框的 Option 的属性值,默认是 Option 的子元素。比如在子元素需要高亮效果时,此值可以设为 `value`。 | string | `children` (combobox 模式下为 `value`) | +| placeholder | 选择框默认文字 | string\|slot | - | +| showSearch | 使单选模式可搜索 | boolean | false | +| showArrow | 是否显示下拉小箭头 | boolean | true | +| size | 选择框大小,可选 `large` `small` | string | default | +| tokenSeparators | 在 tags 和 multiple 模式下自动分词的分隔符 | string\[] | | +| value(v-model) | 指定当前选中的条目 | string\|string\[]\|number\|number\[] | - | + + +> 注意,如果发现下拉菜单跟随页面滚动,或者需要在其他弹层中触发 Select,请尝试使用 `getPopupContainer={triggerNode => triggerNode.parentNode}` 将下拉弹层渲染节点固定在触发器的父元素中。 + +### 事件 +| 事件名称 | 说明 | 回调参数 | +| --- | --- | --- | +| blur | 失去焦点的时回调 | function | +| change | 选中 option,或 input 的 value 变化(combobox 模式下)时,调用此函数 | function(value, option:Option/Array) | +| deselect | 取消选中时调用,参数为选中项的 value (或 key) 值,仅在 multiple 或 tags 模式下生效 | function(value,option:Option) | +| focus | 获得焦点时回调 | function | +| inputKeydown | 键盘按下时回调 | function | +| mouseenter | 鼠标移入时回调 | function | +| mouseleave | 鼠标移出时回调 | function | +| popupScroll | 下拉列表滚动时的回调 | function | +| search | 文本框值变化时回调 | function(value: string) | +| select | 被选中时调用,参数为选中项的 value (或 key) 值 | function(value, option:Option) | + +### Select Methods + +| 名称 | 说明 | +| --- | --- | +| blur() | 取消焦点 | +| focus() | 获取焦点 | + +### Option props + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| disabled | 是否禁用 | boolean | false | +| key | 和 value 含义一致。如果 Vue 需要你设置此项,此项值与 value 的值相同,然后可以省略 value 设置 | string | | +| title | 选中该 Option 后,Select 的 title | string | - | +| value | 默认根据此属性值进行筛选 | string\|number | - | + +### OptGroup props + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| key | | string | - | +| label | 组名 | string\|\|function(h)\|slot | 无 | diff --git a/components/vc-select/DropdownMenu.vue b/components/vc-select/DropdownMenu.vue index 347617414..ef287ac22 100644 --- a/components/vc-select/DropdownMenu.vue +++ b/components/vc-select/DropdownMenu.vue @@ -6,6 +6,7 @@ import { getSelectKeys, preventDefaultEvent } from './util' import { cloneElement } from '../_util/vnode' import BaseMixin from '../_util/BaseMixin' import { getSlotOptions } from '../_util/props-util' +import addEventListener from '../_util/Dom/addEventListener' export default { name: 'DropdownMenu', @@ -32,6 +33,12 @@ export default { mounted () { this.$nextTick(() => { this.scrollActiveItemToView() + const { $refs, $listeners } = this + if ($listeners.popupScroll) { + console.log($refs.menuContainer) + this.menuContainerHandler = addEventListener($refs.menuContainer, + 'scroll', ()=>{console.log(111)}) + } }) this.lastVisible = this.$props.visible }, @@ -42,15 +49,12 @@ export default { } }, }, - - // shouldComponentUpdate (nextProps) { - // if (!nextProps.visible) { - // this.lastVisible = false - // } - // // freeze when hide - // return nextProps.visible - // } - + beforeDestroy () { + if (this.menuContainerHandler) { + this.menuContainerHandler.remove() + this.menuContainerHandler = null + } + }, updated () { const props = this.$props if (!this.prevVisible && props.visible) { @@ -170,13 +174,13 @@ export default { render () { const renderMenu = this.renderMenu() this.prevVisible = this.visible - const { popupFocus, popupScroll } = this.$listeners + const { popupFocus } = this.$listeners return renderMenu ? (
{renderMenu}
diff --git a/components/vc-select/Select.vue b/components/vc-select/Select.vue index b464ddf95..f0cec7fbc 100644 --- a/components/vc-select/Select.vue +++ b/components/vc-select/Select.vue @@ -141,7 +141,16 @@ export default { }) } this.sValue = sValue - this.initLabelAndTitleMap() + + sValue.forEach((val) => { + const key = val.key + let { label, title } = val + label = label === undefined ? this.labelMap.get(key) : label + title = title === undefined ? this.titleMap.get(key) : title + this.labelMap.set(key, label === undefined ? key : label) + this.titleMap.set(key, title) + }) + if (this.combobox) { this.setState({ inputValue: sValue.length ? this.labelMap.get((sValue[0].key)) : '', @@ -382,68 +391,68 @@ export default { } }, - onOuterFocus (e) { - if (this.disabled) { - e.preventDefault() - return - } - this.clearBlurTime() - if ( - !isMultipleOrTagsOrCombobox(this.$props) && - e.target === this.getInputDOMNode() - ) { - return - } - if (this._focused) { - return - } - this._focused = true - this.updateFocusClassName() - this.timeoutFocus() - }, + // onOuterFocus (e) { + // if (this.disabled) { + // e.preventDefault() + // return + // } + // this.clearBlurTime() + // if ( + // !isMultipleOrTagsOrCombobox(this.$props) && + // e.target === this.getInputDOMNode() + // ) { + // return + // } + // if (this._focused) { + // return + // } + // this._focused = true + // this.updateFocusClassName() + // this.timeoutFocus() + // }, onPopupFocus () { // fix ie scrollbar, focus element again this.maybeFocus(true, true) }, - onOuterBlur (e) { - if (this.disabled) { - e.preventDefault() - return - } - this.blurTimer = setTimeout(() => { - this._focused = false - this.updateFocusClassName() - const props = this.$props - let { sValue } = this - const { inputValue } = this - if ( - isSingleMode(props) && - props.showSearch && - inputValue && - props.defaultActiveFirstOption - ) { - const options = this._options || [] - if (options.length) { - const firstOption = findFirstMenuItem(options) - if (firstOption) { - sValue = [ - { - key: firstOption.key, - label: this.getLabelFromOption(firstOption), - }, - ] - this.fireChange(sValue) - } - } - } else if (isMultipleOrTags(props) && inputValue) { - this.inputValue = this.getInputDOMNode().value = '' - } - this.$emit('blur', this.getVLForOnChange(sValue)) - this.setOpenState(false) - }, 10) - }, + // onOuterBlur (e) { + // if (this.disabled) { + // e.preventDefault() + // return + // } + // this.blurTimer = setTimeout(() => { + // this._focused = false + // this.updateFocusClassName() + // const props = this.$props + // let { sValue } = this + // const { inputValue } = this + // if ( + // isSingleMode(props) && + // props.showSearch && + // inputValue && + // props.defaultActiveFirstOption + // ) { + // const options = this._options || [] + // if (options.length) { + // const firstOption = findFirstMenuItem(options) + // if (firstOption) { + // sValue = [ + // { + // key: firstOption.key, + // label: this.getLabelFromOption(firstOption), + // }, + // ] + // this.fireChange(sValue) + // } + // } + // } else if (isMultipleOrTags(props) && inputValue) { + // this.inputValue = this.getInputDOMNode().value = '' + // } + // this.$emit('blur', this.getVLForOnChange(sValue)) + // this.setOpenState(false) + // }, 10) + // }, onClearSelection (event) { const { inputValue, sValue, disabled } = this @@ -524,8 +533,7 @@ export default { } return this.getOptionsFromChildren(value, this.$slots.default) }, - getLabelBySingleValue (children, value) { - console.log('getLabelBySingleValue') + getLabelBySingleValue (children = [], value) { if (value === undefined) { return null } @@ -546,7 +554,7 @@ export default { return label }, - getValueByLabel (children, label) { + getValueByLabel (children = [], label) { if (label === undefined) { return null } @@ -852,7 +860,6 @@ export default { } else { filterFn = defaultFilter } - if (!filterFn) { return true } else if (typeof filterFn === 'function') { @@ -1010,7 +1017,7 @@ export default { }, isChildDisabled (key) { - return this.$slots.default.some(child => { + return (this.$slots.default || []).some(child => { const childValue = getValuePropValue(child) return childValue === key && getValue(child, 'disabled') }) @@ -1420,19 +1427,19 @@ export default { } return null }, - rootRefClick (e) { - // e.stopPropagation() - if (this._focused) { - // this.getInputDOMNode().blur() - this.onOuterBlur() - } else { - this.onOuterFocus() - // this.getInputDOMNode().focus() - } - }, + // rootRefClick (e) { + // // e.stopPropagation() + // if (this._focused) { + // // this.getInputDOMNode().blur() + // this.onOuterBlur() + // } else { + // this.onOuterFocus() + // // this.getInputDOMNode().focus() + // } + // }, selectionRefClick (e) { + console.log('selectionRefClick') e.stopPropagation() - this.clearBlurTime() if (!this.disabled) { const input = this.getInputDOMNode() if (this._focused && this.openStatus) { @@ -1440,9 +1447,7 @@ export default { this.setOpenState(false, false) input && input.blur() } else { - // this._focused = true - // this.updateFocusClassName() - // this.timeoutFocus() + this.clearBlurTime() this._focused = true this.setOpenState(true, true) input && input.focus() @@ -1450,12 +1455,16 @@ export default { } }, selectionRefFocus (e) { - if (this._focused) { + if (this._focused || this.disabled) { return } this._focused = true this.updateFocusClassName() }, + selectionRefBlur (e) { + this._focused = false + this.updateFocusClassName() + }, }, render () { @@ -1486,10 +1495,11 @@ export default { if (!isMultipleOrTagsOrCombobox(props)) { selectionProps.on.keydown = this.onKeyDown selectionProps.on.focus = this.selectionRefFocus + selectionProps.on.blur = this.selectionRefBlur selectionProps.attrs.tabIndex = props.disabled ? -1 : 0 } const rootCls = { - [prefixCls]: 1, + [prefixCls]: true, [`${prefixCls}-open`]: openStatus, [`${prefixCls}-focused`]: openStatus || !!this._focused, [`${prefixCls}-combobox`]: isCombobox(props), diff --git a/components/vc-select/SelectTrigger.vue b/components/vc-select/SelectTrigger.vue index 932a75da1..8dd1f7536 100644 --- a/components/vc-select/SelectTrigger.vue +++ b/components/vc-select/SelectTrigger.vue @@ -141,6 +141,7 @@ export default { getPopupContainer, showAction, } = $props + const { mouseenter, mouseleave, popupFocus, dropdownVisibleChange } = $listeners const dropdownPrefixCls = this.getDropdownPrefixCls() const popupClassName = { [dropdownClassName]: !!dropdownClassName, @@ -153,7 +154,7 @@ export default { inputValue, visible, }, on: { - popupFocus: $listeners.popupFocus, + popupFocus, }, }) let hideAction @@ -186,10 +187,16 @@ export default { popupStyle, }, on: { - popupVisibleChange: $listeners.dropdownVisibleChange, + popupVisibleChange: dropdownVisibleChange, }, ref: 'triggerRef', } + if (mouseenter) { + triggerProps.on.mouseenter = mouseenter + } + if (mouseleave) { + triggerProps.on.mouseleave = mouseleave + } return ( {$slots.default}