add autocomplete

pull/165/head
tangjinzhou 2018-02-28 19:07:04 +08:00
parent 7a0a36499d
commit 6d3860473f
20 changed files with 529 additions and 45 deletions

View File

@ -1,7 +1,11 @@
<script>
import omit from 'omit.js'
import PropTypes from '../_util/vue-types'
import { cloneElement } from '../_util/vnode'
export default {
props: {
value: PropTypes.any,
disabled: PropTypes.bool,
},
methods: {
focus () {
const ele = this.$refs.ele
@ -15,14 +19,15 @@ export default {
},
render () {
const { $slots, $listeners, $props, $attrs } = this
return cloneElement($slots.default, {
const { $slots = {}, $listeners = {}, $props = {}, $attrs = {}} = this
const value = $props.value === undefined ? '' : $props.value
return cloneElement($slots.default[0], {
domProps: {
value: $props.value,
value,
},
props: omit($props, ['value']),
props: $props,
on: $listeners,
attrs: { ...$attrs, value: $props.value },
attrs: { ...$attrs, value },
ref: 'ele',
})
},

View File

@ -0,0 +1,44 @@
<cn>
#### 基本使用
基本使用。通过 dataSource 设置自动完成的数据源
</cn>
<us>
#### Basic Usage
Basic Usage, set datasource of autocomplete with `dataSource` property.
</us>
```html
<template>
<a-auto-complete
:dataSource="dataSource"
style="width: 200px"
@select="onSelect"
@search="handleSearch"
placeholder="input here"
/>
</template>
<script>
export default {
data() {
return {
dataSource: [],
}
},
methods: {
handleSearch(value) {
this.dataSource = !value ? [] : [
value,
value + value,
value + value + value,
]
},
onSelect(value) {
console.log('onSelect', value);
}
}
}
</script>
```

View File

@ -0,0 +1,140 @@
<cn>
#### 查询模式 - 确定类目
查询模式 - 确定类目
</cn>
<us>
#### Lookup-Patterns - Certain Category
Lookup-Patterns - Certain Category
</us>
```html
<template>
<div class="certain-category-search-wrapper" style="width: 250px">
<a-auto-complete
class="certain-category-search"
dropdownClassName="certain-category-search-dropdown"
:dropdownMatchSelectWidth="false"
:dropdownStyle="{width: '300px'}"
size="large"
style="width: 100%"
placeholder="input here"
optionLabelProp="value"
>
<template slot="dataSource">
<a-select-opt-group
v-for="group in dataSource"
:key="group.title"
>
<span slot="label">
{{group.title}}
<a
style="float: right"
href="https://www.google.com/search?q=antd"
target="_blank"
rel="noopener noreferrer"
>更多
</a>
</span>
<a-select-option v-for="opt in group.children" :key="opt.title" :value="opt.title">
{{opt.title}}
<span class="certain-search-item-count">{{opt.count}} 人 关注</span>
</a-select-option>
</a-select-opt-group>
<a-select-option disabled key="all" class="show-all">
<a
href="https://www.google.com/search?q=antd"
target="_blank"
rel="noopener noreferrer"
>
查看所有结果
</a>
</a-select-option>
</template>
<a-input>
<a-icon slot="suffix" type="search" class="certain-category-icon" />
</a-input>
</a-auto-complete>
</div>
</template>
<script>
const dataSource = [{
title: '话题',
children: [{
title: 'AntDesign',
count: 10000,
}, {
title: 'AntDesign UI',
count: 10600,
}],
}, {
title: '问题',
children: [{
title: 'AntDesign UI 有多好',
count: 60100,
}, {
title: 'AntDesign 是啥',
count: 30010,
}],
}, {
title: '文章',
children: [{
title: 'AntDesign 是一个设计语言',
count: 100000,
}],
}];
export default {
data() {
return {
dataSource,
}
},
}
</script>
<style>
.certain-category-search.ant-select-auto-complete .ant-input-affix-wrapper .ant-input-suffix {
right: 12px;
}
.certain-category-search-dropdown .ant-select-dropdown-menu-item-group-title {
color: #666;
font-weight: bold;
}
.certain-category-search-dropdown .ant-select-dropdown-menu-item-group {
border-bottom: 1px solid #F6F6F6;
}
.certain-category-search-dropdown .ant-select-dropdown-menu-item {
padding-left: 16px;
}
.certain-category-search-dropdown .ant-select-dropdown-menu-item.show-all {
text-align: center;
cursor: default;
}
.certain-category-search-dropdown .ant-select-dropdown-menu {
max-height: 300px;
}
.certain-search-item-count {
position: absolute;
color: #999;
right: 16px;
}
.certain-category-search.ant-select-focused .certain-category-icon {
color: #108ee9;
}
.certain-category-icon {
color: #6E6E6E;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
font-size: 16px;
}
</style>
```

View File

@ -0,0 +1,53 @@
<cn>
#### 自定义输入组件
自定义输入组件。
</cn>
<us>
#### Customize Input Component
Customize Input Component
</us>
```html
<template>
<a-auto-complete
:dataSource="dataSource"
style="width: 200px"
@search="handleSearch"
@select="onSelect"
>
<a-textarea
placeholder="input here"
class="custom"
style="height: 50px"
@keydown="handleKeyPress"
/>
</a-auto-complete>
</template>
<script>
export default {
data() {
return {
dataSource: [],
}
},
methods: {
onSelect(value) {
console.log('onSelect', value);
},
handleSearch(value) {
this.dataSource = !value ? [] : [
value,
value + value,
value + value + value,
]
},
handleKeyPress(ev) {
console.log('handleKeyPress', ev);
}
}
}
</script>
```

View File

@ -0,0 +1,36 @@
<cn>
#### 不区分大小写
不区分大小写的 AutoComplete
</cn>
<us>
#### Non-case-sensitive AutoComplete
A non-case-sensitive AutoComplete
</us>
```html
<template>
<a-auto-complete
:dataSource="dataSource"
style="width: 200px"
placeholder="input here"
:filterOption="filterOption"
/>
</template>
<script>
export default {
data() {
return {
dataSource: ['Burns Bay Road', 'Downing Street', 'Wall Street'],
}
},
methods: {
filterOption(input, option) {
return option.componentOptions.children[0].text.toUpperCase().indexOf(input.toUpperCase()) >= 0
}
}
}
</script>
```

View File

@ -0,0 +1,45 @@
<cn>
#### 自定义选项
也可以直接传递slot="dataSource"的Option
</cn>
<us>
#### Customized
You could pass `slot="dataSource` as children of `AutoComplete`, instead of using `dataSource`
</us>
```html
<template>
<a-auto-complete
style="width: 200px"
@search="handleSearch"
placeholder="input here"
>
<template slot="dataSource">
<a-select-option v-for="email in result" :key="email">{{email}}</a-select-option>
</template>
</a-auto-complete>
</template>
<script>
export default {
data() {
return {
result: [],
}
},
methods: {
handleSearch(value) {
let result;
if (!value || value.indexOf('@') >= 0) {
result = [];
} else {
result = ['gmail.com', '163.com', 'qq.com'].map(domain => `${value}@${domain}`);
}
this.result = result
},
}
}
</script>
```

View File

@ -0,0 +1,110 @@
<cn>
#### 查询模式 - 不确定类目
查询模式 - 不确定类目
</cn>
<us>
#### Lookup-Patterns - Uncertain Category
Lookup-Patterns - Uncertain Category
</us>
```html
<template>
<div class="global-search-wrapper" style="width: 300px">
<a-auto-complete
class="global-search"
size="large"
style="width: 100%"
@select="onSelect"
@search="handleSearch"
placeholder="input here"
optionLabelProp="text"
>
<template slot="dataSource">
<a-select-option v-for="item in dataSource" :key="item.category" :text="item.category">
{{item.query}} 在
<a
:href="`https://s.taobao.com/search?q=${item.query}`"
target="_blank"
rel="noopener noreferrer"
>
{{item.category}}
</a>
区块中
<span className="global-search-item-count">约 {{item.count}} 个结果</span>
</a-select-option>
</template>
<a-input>
<a-button slot="suffix" class="search-btn" size="large" type="primary">
<a-icon type="search" />
</a-button>
</a-input>
</a-auto-complete>
</div>
</template>
<script>
export default {
data() {
return {
dataSource: [],
}
},
methods: {
onSelect(value) {
console.log('onSelect', value);
},
handleSearch(value) {
this.dataSource = value ? this.searchResult(value) : []
},
getRandomInt(max, min = 0) {
return Math.floor(Math.random() * (max - min + 1)) + min;
},
searchResult(query) {
return (new Array(this.getRandomInt(5))).join('.').split('.')
.map((item, idx) => ({
query,
category: `${query}${idx}`,
count: this.getRandomInt(200, 100),
}));
}
}
}
</script>
<style>
.global-search-wrapper {
padding-right: 50px;
}
.global-search {
width: 100%;
}
.global-search.ant-select-auto-complete .ant-select-selection--single {
margin-right: -46px;
}
.global-search.ant-select-auto-complete .ant-input-affix-wrapper .ant-input:not(:last-child) {
padding-right: 62px;
}
.global-search.ant-select-auto-complete .ant-input-affix-wrapper .ant-input-suffix {
right: 0;
}
.global-search.ant-select-auto-complete .ant-input-affix-wrapper .ant-input-suffix button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.global-search-item-count {
position: absolute;
right: 16px;
}
</style>
```

View File

@ -1,6 +1,5 @@
<script>
import { Option, OptGroup } from './vc-select'
import clonedeep from 'lodash.clonedeep'
import { Option, OptGroup } from '../vc-select'
import Select, { AbstractSelectProps, SelectValue } from '../select'
import Input from '../input'
import InputElement from './InputElement'
@ -25,8 +24,9 @@ const AutoCompleteProps = {
...AbstractSelectProps,
value: SelectValue,
defaultValue: SelectValue,
dataSource: DataSourceItemType,
dataSource: PropTypes.arrayOf(DataSourceItemType),
optionLabelProp: String,
dropdownMatchSelectWidth: PropTypes.bool,
// onChange?: (value: SelectValue) => void;
// onSelect?: (value: SelectValue, option: Object) => any;
}
@ -39,19 +39,30 @@ export default {
transitionName: PropTypes.string.def('slide-up'),
choiceTransitionName: PropTypes.string.def('zoom'),
optionLabelProp: PropTypes.string.def('children'),
filterOption: clonedeep(AbstractSelectProps.filterOption).def(false),
filterOption: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.func,
]).def(false),
defaultActiveFirstOption: PropTypes.bool.def(true),
},
Option,
OptGroup,
model: {
prop: 'value',
event: 'change',
},
methods: {
getInputElement () {
const { $slots } = this
const children = filterEmpty($slots.default)
const element = children.length ? children : <Input />
console.log(element)
// const elementProps = { ...element.props }
const element = children.length ? children[0] : <Input />
const { componentOptions = {}} = element
const { listeners = {}} = componentOptions
const elementProps = {
on: listeners,
}
return (
<InputElement>{element}</InputElement>
<InputElement {...elementProps}>{element}</InputElement>
)
},
@ -104,16 +115,23 @@ export default {
}
}) : []
}
const selectProps = {
props: {
...getOptionProps(this),
mode: 'combobox',
optionLabelProp,
getInputElement: this.getInputElement,
notFoundContent: getComponentFromProp(this, 'notFoundContent'),
},
class: cls,
ref: 'select',
on: {
...$listeners,
},
}
return (
<Select
{...getOptionProps(this)}
class={cls}
mode='combobox'
optionLabelProp={optionLabelProp}
getInputElement={this.getInputElement}
notFoundContent={getComponentFromProp(this, 'notFoundContent')}
ref='select'
on={$listeners}
{...selectProps}
>
{options}
</Select>

View File

@ -33,7 +33,14 @@ export { default as Badge } from './badge'
export { default as Tabs } from './tabs'
export { default as Input } from './input'
import Input from './input'
const InputGroup = Input.Group
const InputSearch = Input.Search
const InputTextArea = Input.TextArea
const Textarea = InputTextArea
export { Input, InputGroup, InputSearch, InputTextArea, Textarea }
export { default as Breadcrumb } from './breadcrumb'
@ -80,3 +87,5 @@ export { default as Switch } from './switch'
export { default as LocaleProvider } from './locale-provider'
export { default as AutoComplete } from './auto-complete'

View File

@ -48,6 +48,7 @@ export default {
}
this.$emit('change.value', e.target.value)
this.$emit('change', e)
this.$emit('input', e)
},
focus () {

View File

@ -94,6 +94,7 @@ export default {
this.$emit('change.value', e.target.value)
this.$emit('change', e)
}
this.$emit('input', e)
},
focus () {
@ -106,23 +107,33 @@ export default {
},
render () {
const { stateValue, getTextAreaClassName, handleKeyDown, handleTextareaChange, textareaStyles } = this
const { stateValue,
getTextAreaClassName,
handleKeyDown,
handleTextareaChange,
textareaStyles,
$attrs,
$listeners,
} = this
const otherProps = omit(this.$props, [
'prefixCls',
'autosize',
'type',
])
const attrs = {
attrs: { ...otherProps, ...this.$attrs },
const textareaProps = {
attrs: { ...otherProps, ...$attrs },
on: {
...$listeners,
keydown: handleKeyDown,
input: handleTextareaChange,
},
}
return (
<textarea
{...attrs}
{...textareaProps}
value={stateValue}
class={getTextAreaClassName()}
style={textareaStyles}
onKeydown={handleKeyDown}
onInput={handleTextareaChange}
ref='textArea'
/>
)

View File

@ -25,6 +25,8 @@ const AbstractSelectProps = {
PropTypes.bool,
PropTypes.func,
]),
autoFocus: PropTypes.bool,
backfill: PropTypes.bool,
}
const Value = PropTypes.shape({
key: String,
@ -64,7 +66,6 @@ const SelectProps = {
getPopupContainer: PropTypes.func,
tokenSeparators: PropTypes.arrayOf(PropTypes.string),
getInputElement: PropTypes.func,
autoFocus: PropTypes.bool,
}
const SelectPropTypes = {

View File

@ -23,3 +23,4 @@ import './message/style'
import './spin/style'
import './select/style'
import './switch/style'
import './auto-complete/style'

View File

@ -1,7 +1,7 @@
import PropTypes from '../_util/vue-types'
const triggerType = PropTypes.oneOf(['hover', 'focus', 'click'])
export default () => ({
// trigger: PropTypes.oneOf(['hover', 'focus', 'click']).def('hover'),
trigger: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]).def('hover'),
trigger: PropTypes.oneOfType([triggerType, PropTypes.arrayOf(triggerType)]).def('hover'),
visible: PropTypes.bool,
placement: PropTypes.oneOf(['top', 'left', 'right', 'bottom',
'topLeft', 'topRight', 'bottomLeft', 'bottomRight',

View File

@ -16,6 +16,7 @@ const props = {
mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']).def('vertical'),
parentMenu: PropTypes.object,
multiple: PropTypes.bool,
value: PropTypes.any,
// clearSubMenuTimers: PropTypes.func.def(noop),
}
const MenuItem = {

View File

@ -197,9 +197,10 @@ export default {
// visible: props.visible,
},
class: className,
on: {
...this.$listeners,
},
on: {},
}
if (this.$listeners.popupScroll) {
domProps.on.scroll = this.$listeners.popupScroll
}
if (props.id) {
domProps.id = props.id

View File

@ -32,10 +32,12 @@ export const SelectPropTypes = {
value: PropTypes.any,
defaultValue: PropTypes.any,
dropdownStyle: PropTypes.object,
dropdownClassName: PropTypes.string,
maxTagTextLength: PropTypes.number,
maxTagCount: PropTypes.number,
maxTagPlaceholder: PropTypes.any,
tokenSeparators: PropTypes.arrayOf(PropTypes.string),
getInputElement: PropTypes.func,
showAction: PropTypes.arrayOf(PropTypes.string),
autoFocus: PropTypes.bool,
}

View File

@ -8,7 +8,7 @@ import warning from 'warning'
import Option from './Option'
import { hasProp, getSlotOptions, getPropsData, getValueByProp as getValue, getComponentFromProp, getEvents, getClass } from '../_util/props-util'
import getTransitionProps from '../_util/getTransitionProps'
import { cloneElement } from '../_util/vnode'
import { cloneElement, cloneVNode } from '../_util/vnode'
import BaseMixin from '../_util/BaseMixin'
import {
getPropValue,
@ -361,7 +361,7 @@ export default {
this.fireChange(sValue)
let inputValue
if (isCombobox(props)) {
inputValue = getPropValue(item, props.optionLabelProp)
inputValue = selectedValue
} else {
inputValue = ''
}
@ -707,13 +707,19 @@ export default {
const inputCls = classnames(getClass(inputElement), {
[`${props.prefixCls}-search__field`]: true,
})
// const inputElement = cloneVNode(inputElement, true)
const inputEvents = getEvents(inputElement)
console.log(inputElement, this.inputValue)
// https://github.com/ant-design/ant-design/issues/4992#issuecomment-281542159
// Add space to the end of the inputValue as the width measurement tolerance
inputElement.data = inputElement.data || {}
return (
<div class={`${props.prefixCls}-search__field__wrap`}>
{cloneElement(inputElement, {
props: {
disabled: props.disabled,
value: this.inputValue,
},
attrs: {
...(inputElement.data.attrs || {}),
disabled: props.disabled,
@ -1192,7 +1198,7 @@ export default {
label = key
}
sel.push(
<MenuItemGroup key={key} title={label}>
<MenuItemGroup key={key} title={label} class ={getClass(child)}>
{innerItems}
</MenuItemGroup>
)
@ -1209,7 +1215,6 @@ export default {
const childValue = getValuePropValue(child)
validateOptionValue(childValue, this.$props)
if (this._filterOption(inputValue, child)) {
const p = {
attrs: UNSELECTABLE_ATTRIBUTE,
@ -1220,6 +1225,7 @@ export default {
},
style: UNSELECTABLE_STYLE,
on: getEvents(child),
class: getClass(child),
}
const menuItem = (
<MenuItem {...p}>{child.componentOptions.children}</MenuItem>
@ -1508,7 +1514,7 @@ export default {
return (
<SelectTrigger
dropdownAlign={props.dropdownAlign}
dropdownClass={props.dropdownClassName}
dropdownClassName={props.dropdownClassName}
dropdownMatchSelectWidth={props.dropdownMatchSelectWidth}
defaultActiveFirstOption={props.defaultActiveFirstOption}
dropdownMenuStyle={props.dropdownMenuStyle}

View File

@ -24,11 +24,11 @@ export function getPropValue (child, prop) {
return getValuePropValue(child)
}
if (prop === 'children') {
if (child.$slots) {
return cloneVNodes(child.$slots.default, true)
} else {
return cloneVNodes(child.componentOptions.children, true)
const newChild = child.$slots ? cloneVNodes(child.$slots.default, true) : cloneVNodes(child.componentOptions.children, true)
if (newChild.length === 1 && !newChild[0].tag) {
return newChild[0].text
}
return newChild
}
const data = getPropsData(child)
if (prop in data) {

View File

@ -3,7 +3,7 @@ const AsyncComp = () => {
const hashs = window.location.hash.split('/')
const d = hashs[hashs.length - 1]
return {
component: import(`../components/switch/demo/${d}`),
component: import(`../components/auto-complete/demo/${d}`),
}
}
export default [