feat: add tree-select

pull/77/merge
tangjinzhou 2018-07-11 17:51:20 +08:00
parent c0fb36468f
commit 7cd4f39756
49 changed files with 4633 additions and 8 deletions

View File

@ -117,6 +117,16 @@ const getComponentFromProp = (instance, prop) => {
}
}
const getAllProps = (ele) => {
let data = ele.data || {}
let componentOptions = ele.componentOptions || {}
if (ele.$vnode) {
data = ele.$vnode.data || {}
componentOptions = ele.$vnode.componentOptions || {}
}
return { ...data.props, ...data.attrs, ...componentOptions.propsData }
}
const getPropsData = (ele) => {
let componentOptions = ele.componentOptions
if (ele.$vnode) {
@ -247,5 +257,6 @@ export {
isValidElement,
camelize,
getSlots,
getAllProps,
}
export default hasProp

View File

@ -21,7 +21,7 @@ import { getComponentFromProp, getOptionProps, filterEmpty, isValidElement } fro
// }
const AutoCompleteProps = {
...AbstractSelectProps,
...AbstractSelectProps(),
value: SelectValue,
defaultValue: SelectValue,
dataSource: PropTypes.array,

View File

@ -102,7 +102,7 @@ import { default as Transfer } from './transfer'
import { default as Tree } from './tree'
// import { default as TreeSelect } from './tree-select'
import { default as TreeSelect } from './tree-select'
import { default as Tabs } from './tabs'
@ -196,6 +196,8 @@ const components = [
Transfer,
Tree,
Tree.TreeNode,
TreeSelect,
TreeSelect.TreeNode,
Tabs,
Tabs.TabPane,
Tag,
@ -269,6 +271,7 @@ export {
Table,
Transfer,
Tree,
TreeSelect,
Tabs,
Tag,
TimePicker,

View File

@ -5,7 +5,7 @@ import LocaleReceiver from '../locale-provider/LocaleReceiver'
import defaultLocale from '../locale-provider/default'
import { getComponentFromProp, getOptionProps, filterEmpty } from '../_util/props-util'
const AbstractSelectProps = {
const AbstractSelectProps = () => ({
prefixCls: PropTypes.string,
size: PropTypes.oneOf(['small', 'large', 'default']),
notFoundContent: PropTypes.any,
@ -28,7 +28,7 @@ const AbstractSelectProps = {
autoFocus: PropTypes.bool,
backfill: PropTypes.bool,
showArrow: PropTypes.bool,
}
})
const Value = PropTypes.shape({
key: PropTypes.string,
}).loose
@ -45,7 +45,7 @@ const SelectValue = PropTypes.oneOfType([
])
const SelectProps = {
...AbstractSelectProps,
...AbstractSelectProps(),
value: SelectValue,
defaultValue: SelectValue,
mode: PropTypes.oneOf(['default', 'multiple', 'tags', 'combobox']),

View File

@ -46,3 +46,4 @@ import './layout/style'
import './form/style'
import './anchor/style'
import './list/style'
import './tree-select/style'

View File

@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/tree-select/demo/basic.md correctly 1`] = `
<span class="ant-select ant-select-enabled ant-select-allow-clear ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" tabindex="0" class="ant-select-selection
ant-select-selection--single"><span class="ant-select-selection__rendered"><span class="ant-select-selection__placeholder">Please select</span></span><span class="ant-select-arrow" style="outline: none;"><b></b></span></span>
</span>
`;
exports[`renders ./components/tree-select/demo/checkable.md correctly 1`] = `
<span class="ant-select ant-select-enabled ant-select ant-select-enabled" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" class="ant-select-selection
ant-select-selection--multiple"><div class="ant-select-selection__rendered"><li title="Node1" unselectable="unselectable" class="ant-select-selection__choice"><span class="ant-select-selection__choice__remove"></span><span class="ant-select-selection__choice__content">Node1</span></li>
<li
class="ant-select-search ant-select-search--inline"><span class="ant-select-search__field__wrap"><input role="textbox" class="ant-select-search__field"><span class="ant-select-search__field__mirror">&nbsp;</span></span>
</li>
</div><span class="ant-select-search__field__placeholder" style="display: none;">Please select</span></span>
</span>
`;
exports[`renders ./components/tree-select/demo/multiple.md correctly 1`] = `
<span class="ant-select ant-select-enabled ant-select-allow-clear ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" class="ant-select-selection
ant-select-selection--multiple"><div class="ant-select-selection__rendered"><li class="ant-select-search ant-select-search--inline"><span class="ant-select-search__field__wrap"><input role="textbox" class="ant-select-search__field"><span class="ant-select-search__field__mirror">&nbsp;</span></span>
</li>
</div><span class="ant-select-search__field__placeholder" style="display: block;">Please select</span></span>
</span>
`;
exports[`renders ./components/tree-select/demo/treeData.md correctly 1`] = `
<span class="ant-select ant-select-enabled ant-select ant-select-enabled" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" tabindex="0" class="ant-select-selection
ant-select-selection--single"><span class="ant-select-selection__rendered"><span class="ant-select-selection__placeholder">Please select</span></span><span class="ant-select-arrow" style="outline: none;"><b></b></span></span>
</span>
`;

View File

@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest'
demoTest('tree-select')

View File

@ -0,0 +1,6 @@
import TreeSelect from '..'
import focusTest from '../../../tests/shared/focusTest'
describe('TreeSelect', () => {
focusTest(TreeSelect)
})

View File

@ -0,0 +1,53 @@
<cn>
#### 基本用法
最简单的用法。
</cn>
<us>
#### Basic
The most basic usage.
</us>
```html
<template>
<a-tree-select
showSearch
style="width: 300px"
:value="value"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
placeholder='Please select'
allowClear
treeDefaultExpandAll
@change="onChange"
>
<a-tree-select-node value='parent 1' title='parent 1' key='0-1'>
<a-tree-select-node value='parent 1-0' title='parent 1-0' key='0-1-1'>
<a-tree-select-node value='leaf1' title='my leaf' key='random' />
<a-tree-select-node value='leaf2' title='your leaf' key='random1' />
</a-tree-select-node>
<a-tree-select-node value='parent 1-1' title='parent 1-1' key='random2'>
<a-tree-select-node value='sss' key='random3'>
<b style="color: #08c" slot="title">sss</b>
</a-tree-select-node>
</a-tree-select-node>
</a-tree-select-node>
</a-tree-select>
</template>
<script>
export default {
data () {
return {
value: undefined,
}
},
methods: {
onChange (value) {
console.log(arguments)
this.value = value
},
},
}
</script>
```

View File

@ -0,0 +1,73 @@
<cn>
#### 可勾选
使用勾选框实现多选功能。
</cn>
<us>
#### Checkable
Multiple and checkable.
</us>
```html
<template>
<a-tree-select
style="width: 300px"
:treeData="treeData"
:value="value"
@change="onChange"
treeCheckable
:showCheckedStrategy="SHOW_PARENT"
searchPlaceholder='Please select'
treeNodeFilterProp='label'
/>
</template>
<script>
import { TreeSelect } from 'vue-antd-ui'
const SHOW_PARENT = TreeSelect.SHOW_PARENT
const treeData = [{
label: 'Node1',
value: '0-0',
key: '0-0',
children: [{
label: 'Child Node1',
value: '0-0-0',
key: '0-0-0',
}],
}, {
label: 'Node2',
value: '0-1',
key: '0-1',
children: [{
label: 'Child Node3',
value: '0-1-0',
key: '0-1-0',
disabled: true,
}, {
label: 'Child Node4',
value: '0-1-1',
key: '0-1-1',
}, {
label: 'Child Node5',
value: '0-1-2',
key: '0-1-2',
}],
}]
export default {
data () {
return {
value: ['0-0-0'],
treeData,
SHOW_PARENT,
}
},
methods: {
onChange (value) {
console.log('onChange ', value, arguments)
this.value = value
},
},
}
</script>
```

View File

@ -0,0 +1,48 @@
<script>
import Basic from './basic'
import Checkable from './checkable'
import Multiple from './multiple'
import TreeData from './treeData'
import CN from '../index.zh-CN.md'
import US from '../index.en-US.md'
const md = {
cn: `# 树型选择控件。
## 何时使用
类似 Select 的选择控件可选择的数据结构是一个树形结构时可以使用 TreeSelect例如公司层级学科系统分类目录等等
## 代码演示`,
us: `# TreeSelect
## When To Use
\`TreeSelect\` is similar to \`Select\`, but the values are provided in a tree like structure.
Any data whose entries are defined in a hierarchical manner is fit to use this control. Examples of such case may include a corporate hierarchy, a directory structure, and so on.
## Examples
`,
}
export default {
category: 'Components',
subtitle: '树选择',
type: 'Data Entry',
title: 'TreeSelect',
render () {
return (
<div>
<md cn={md.cn} us={md.us}/>
<Basic/>
<Checkable/>
<Multiple/>
<TreeData/>
<api>
<template slot='cn'>
<CN/>
</template>
<US/>
</api>
</div>
)
},
}
</script>

View File

@ -0,0 +1,62 @@
<cn>
#### 多选
多选的树选择。
</cn>
<us>
#### Multiple Selection
Multiple selection usage.
</us>
```html
<template>
<a-tree-select
showSearch
style="width: 300px"
:value="value"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
placeholder='Please select'
allowClear
multiple
treeDefaultExpandAll
@change="onChange"
@search="onSearch"
@select="onSelect"
>
<a-tree-select-node value='parent 1' title='parent 1' key='0-1'>
<a-tree-select-node value='parent 1-0' title='parent 1-0' key='0-1-1'>
<a-tree-select-node value='leaf1' title='my leaf' key='random' />
<a-tree-select-node value='leaf2' title='your leaf' key='random1' />
</a-tree-select-node>
<a-tree-select-node value='parent 1-1' title='parent 1-1' key='random2'>
<a-tree-select-node value='sss' key='random3'>
<b style="color: #08c" slot="title">sss</b>
</a-tree-select-node>
</a-tree-select-node>
</a-tree-select-node>
</a-tree-select>
</template>
<script>
export default {
data () {
return {
value: undefined,
}
},
methods: {
onChange (value) {
console.log(arguments)
this.value = value
},
onSearch () {
console.log(arguments)
},
onSelect () {
console.log(arguments)
},
},
}
</script>
```

View File

@ -0,0 +1,63 @@
<cn>
#### 从数据直接生成
使用 `treeData` 把 JSON 数据直接生成树结构。
</cn>
<us>
#### Generate form tree data
The tree structure can be populated using `treeData` property. This is a quick and easy way to provide the tree content.
</us>
```html
<template>
<a-tree-select
style="width: 300px"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
:treeData="treeData"
placeholder='Please select'
treeDefaultExpandAll
labelInValue
v-model="value"
>
<span style="color: #08c" slot="label" slot-scope="{key, value}" v-if="key='0-0-1'">
<a-icon type="home"/>Child Node1 {{value}}
</span>
</a-tree-select>
</template>
<script>
const treeData = [{
label: 'Node1',
value: '0-0',
key: '0-0',
children: [{
value: '0-0-1',
key: '0-0-1',
scopedSlots: { // custom label
label: 'label',
},
}, {
label: 'Child Node2',
value: '0-0-2',
key: '0-0-2',
}],
}, {
label: 'Node2',
value: '0-1',
key: '0-1',
}]
export default {
data () {
return {
value: undefined,
treeData,
}
},
watch: {
value (value) {
console.log(value)
},
},
}
</script>
```

View File

@ -0,0 +1,61 @@
## API
### Tree props
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| allowClear | Whether allow clear | boolean | false |
| defaultValue | To set the initial selected treeNode(s). | string\|string\[] | - |
| disabled | Disabled or not | boolean | false |
| dropdownClassName | className of dropdown menu | string | - |
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width | boolean | true |
| dropdownStyle | To set the style of the dropdown menu | object | - |
| filterTreeNode | Whether to filter treeNodes by input value. The value of `treeNodeFilterProp` is used for filtering by default. | boolean\|Function(inputValue: string, treeNode: TreeNode) (should return boolean) | Function |
| getPopupContainer | To set the container of the dropdown menu. The default is to create a `div` element in `body`, you can reset it to the scrolling area and make a relative reposition. [example](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | Function(triggerNode) | () => document.body |
| labelInValue | whether to embed label in value, turn the format of value from `string` to `{value: string, label: VNode, halfChecked: string[]}` | boolean | false |
| loadData | Load data asynchronously. | function(node) | - |
| multiple | Support multiple or not, will be `true` when enable `treeCheckable`. | boolean | false |
| placeholder | Placeholder of the select input | string\|slot | - |
| searchPlaceholder | Placeholder of the search input | string\|slot | - |
| showCheckedStrategy | The way show selected item in box. **Default:** just show child nodes. **`TreeSelect.SHOW_ALL`:** show all checked treeNodes (include parent treeNode). **`TreeSelect.SHOW_PARENT`:** show checked treeNodes (just show parent treeNode). | enum { TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD |
| showSearch | Whether to display a search input in the dropdown menu(valid only in the single mode) | boolean | false |
| size | To set the size of the select input, options: `large` `small` | string | 'default' |
| treeCheckable | Whether to show checkbox on the treeNodes | boolean | false |
| treeCheckStrictly | Whether to check nodes precisely (in the `checkable` mode), means parent and child nodes are not associated, and it will make `labelInValue` be true | boolean | false |
| treeData | Data of the treeNodes, manual construction work is no longer needed if this property has been set(ensure the Uniqueness of each value) | array&lt;{ value, label, children, [disabled, disableCheckbox, selectable] }> | \[] |
| treeDataSimpleMode | Enable simple mode of treeData.(treeData should like this: [{id:1, pId:0, value:'1', label:"test1",...},...], pId is parent node's id) | false\|Array&lt;{ id: string, pId: string, rootPId: null }> | false |
| treeDefaultExpandAll | Whether to expand all treeNodes by default | boolean | false |
| treeDefaultExpandedKeys | Default expanded treeNodes | string\[] | - |
| treeNodeFilterProp | Will be used for filtering if `filterTreeNode` returns true | string | 'value' |
| treeNodeLabelProp | Will render as content of select | string | 'title' |
| value(v-model) | To set the current selected treeNode(s). | string\|string\[] | - |
### Events
| Events Name | Description | Arguments |
| --- | --- | --- |
| change | A callback function, can be executed when selected treeNodes or input value change | function(value, label, extra) |
| search | A callback function, can be executed when the search input changes. | function(value: string) |
| select | A callback function, can be executed when you select a treeNode. | function(value, node, extra) |
### Tree Methods
| Name | Description |
| ---- | ----------- |
| blur() | remove focus |
| focus() | get focus |
### TreeNode props
> We recommend you to use `treeData` rather than `TreeNode`, to avoid the trouble of manual construction.
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| disableCheckbox | Disables the checkbox of the treeNode | boolean | false |
| disabled | Disabled or not | boolean | false |
| isLeaf | Leaf node or not | boolean | false |
| key | Required property, should be unique in the tree | string | - |
| title | Content showed on the treeNodes | string\|slot | '---' |
| value | Will be treated as `treeNodeFilterProp` by default, should be unique in the tree | string | - |
| scopedSlots | When using treeNodes, you can use this property to configure the properties that support the slot, such as `scopedSlots: { title: 'XXX'}` | object | - |

View File

@ -0,0 +1,115 @@
import VcTreeSelect, { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from '../vc-tree-select'
import classNames from 'classnames'
import { TreeSelectProps } from './interface'
import LocaleReceiver from '../locale-provider/LocaleReceiver'
import warning from '../_util/warning'
import { initDefaultProps, getOptionProps, getComponentFromProp, filterEmpty } from '../_util/props-util'
export { TreeData, TreeSelectProps } from './interface'
export default {
TreeNode: { ...TreeNode, name: 'ATreeSelectNode' },
SHOW_ALL,
SHOW_PARENT,
SHOW_CHILD,
name: 'ATreeSelect',
props: initDefaultProps(TreeSelectProps(), {
prefixCls: 'ant-select',
transitionName: 'slide-up',
choiceTransitionName: 'zoom',
showSearch: false,
}),
model: {
prop: 'value',
event: 'change',
},
created () {
warning(
this.multiple !== false || !this.treeCheckable,
'`multiple` will alway be `true` when `treeCheckable` is true',
)
},
methods: {
focus () {
this.$refs.vcTreeSelect.focus()
},
blur () {
this.$refs.vcTreeSelect.blur()
},
onChange () {
this.$emit('change', ...arguments)
},
updateTreeData (list = []) {
for (let i = 0, len = list.length; i < len; i++) {
const { label, title, scopedSlots = {}, children } = list[i]
const { $scopedSlots } = this
let newLabel = typeof label === 'function' ? label(this.$createElement) : label
let newTitle = typeof title === 'function' ? title(this.$createElement) : title
if (!newLabel && scopedSlots.label && $scopedSlots[scopedSlots.label]) {
newLabel = $scopedSlots.label(list[i])
}
if (!newTitle && scopedSlots.title && $scopedSlots[scopedSlots.title]) {
newTitle = $scopedSlots.title(list[i])
}
const item = {
// label: newLabel,
title: newTitle || newLabel,
}
this.updateTreeData(children)
Object.assign(list[i], item)
}
},
renderTreeSelect (locale) {
const props = getOptionProps(this)
const {
prefixCls,
size,
notFoundContent,
dropdownStyle,
dropdownClassName,
...restProps
} = props
this.updateTreeData(props.treeData)
const cls = {
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
}
let checkable = getComponentFromProp(this, 'treeCheckable')
if (checkable) {
checkable = <span class={`${prefixCls}-tree-checkbox-inner`} />
}
const VcTreeSelectProps = {
props: {
...restProps,
dropdownClassName: classNames(dropdownClassName, `${prefixCls}-tree-dropdown`),
prefixCls,
dropdownStyle: { maxHeight: '100vh', overflow: 'auto', ...dropdownStyle },
treeCheckable: checkable,
notFoundContent: notFoundContent || locale.notFoundContent,
},
class: cls,
on: { ...this.$listeners, change: this.onChange },
ref: 'vcTreeSelect',
}
return (
<VcTreeSelect {...VcTreeSelectProps}>{filterEmpty(this.$slots.default)}</VcTreeSelect>
)
},
},
render () {
return (
<LocaleReceiver
componentName='Select'
defaultLocale={{}}
scopedSlots={
{ default: this.renderTreeSelect }
}
/>
)
},
}

View File

@ -0,0 +1,61 @@
## API
### Tree props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| allowClear | 显示清除按钮 | boolean | false |
| defaultValue | 指定默认选中的条目 | string/string\[] | - |
| disabled | 是否禁用 | boolean | false |
| dropdownClassName | 下拉菜单的 className 属性 | string | - |
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽 | boolean | true |
| dropdownStyle | 下拉菜单的样式 | object | - |
| filterTreeNode | 是否根据输入项进行筛选,默认用 treeNodeFilterProp 的值作为要筛选的 TreeNode 的属性值 | boolean\|Function(inputValue: string, treeNode: TreeNode) (函数需要返回bool值) | Function |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | () => document.body |
| labelInValue | 是否把每个选项的 label 包装到 value 中,会把 value 类型从 `string` 变为 `{value: string, label: VNode, halfChecked(treeCheckStrictly 时有效): string[] }` 的格式 | boolean | false |
| loadData | 异步加载数据 | function(node) | - |
| multiple | 支持多选(当设置 treeCheckable 时自动变为true | boolean | false |
| placeholder | 选择框默认文字 | string\|slot | - |
| searchPlaceholder | 搜索框默认文字 | string\|slot | - |
| showCheckedStrategy | 定义选中项回填的方式。`TreeSelect.SHOW_ALL`: 显示所有选中节点(包括父节点). `TreeSelect.SHOW_PARENT`: 只显示父节点(当父节点下所有子节点都选中时). 默认只显示子节点. | enum{TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD |
| showSearch | 在下拉中显示搜索框(仅在单选模式下生效) | boolean | false |
| size | 选择框大小,可选 `large` `small` | string | 'default' |
| treeCheckable | 显示 checkbox | boolean | false |
| treeCheckStrictly | checkable 状态下节点选择完全受控(父子节点选中状态不再关联),会使得 `labelInValue` 强制为 true | boolean | false |
| treeData | treeNodes 数据,如果设置则不需要手动构造 TreeNode 节点value 在整个树范围内唯一) | array&lt;{value, label, children, [disabled, disableCheckbox, selectable]}> | \[] |
| treeDataSimpleMode | 使用简单格式的 treeData具体设置参考可设置的类型 (此时 treeData 应变为这样的数据结构: [{id:1, pId:0, value:'1', label:"test1",...},...], `pId` 是父节点的 id) | false\|Array&lt;{ id: string, pId: string, rootPId: null }> | false |
| treeDefaultExpandAll | 默认展开所有树节点 | boolean | false |
| treeDefaultExpandedKeys | 默认展开的树节点 | string\[] | - |
| treeNodeFilterProp | 输入项过滤对应的 treeNode 属性 | string | 'value' |
| treeNodeLabelProp | 作为显示的 prop 设置 | string | 'title' |
| value(v-model) | 指定当前选中的条目 | string/string\[] | - |
### 事件
| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| change | 选中树节点时调用此函数 | function(value, label, extra) | - |
| search | 文本框值变化时回调 | function(value: string) | - |
| select | 被选中时调用 | function(value, node, extra) | - |
### Tree 方法
| 名称 | 描述 |
| --- | --- |
| blur() | 移除焦点 |
| focus() | 获取焦点 |
### TreeNode props
> 建议使用 treeData 来代替 TreeNode免去手工构造麻烦
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| disableCheckbox | 禁掉 checkbox | boolean | false |
| disabled | 是否禁用 | boolean | false |
| isLeaf | 是否是叶子节点 | boolean | false |
| key | 此项必须设置(其值在整个树范围内唯一) | string | - |
| title | 树节点显示的内容 | string\|slot | '---' |
| value | 默认根据此属性值进行筛选(其值在整个树范围内唯一) | string | - |
| scopedSlots | 使用treeData时可以通过该属性配置支持slot的属性`scopedSlots: { title: 'XXX'}` | object | - |

View File

@ -0,0 +1,37 @@
import PropTypes from '../_util/vue-types'
import { AbstractSelectProps } from '../select'
export const TreeData = PropTypes.shape({
key: PropTypes.string,
value: PropTypes.string,
label: PropTypes.any,
scopedSlots: PropTypes.object,
children: PropTypes.array,
}).loose
export const TreeSelectProps = () => ({
...AbstractSelectProps(),
value: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.array]),
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
multiple: PropTypes.bool,
// onSelect: (value: any) => void,
// onChange: (value: any, label: any) => void,
// onSearch: (value: any) => void,
searchPlaceholder: PropTypes.string,
dropdownClassName: PropTypes.string,
dropdownStyle: PropTypes.object,
dropdownMatchSelectWidth: PropTypes.bool,
treeDefaultExpandAll: PropTypes.bool,
treeCheckable: PropTypes.bool,
treeDefaultExpandedKeys: PropTypes.arrayOf(String),
filterTreeNode: PropTypes.func,
treeNodeFilterProp: PropTypes.string,
treeNodeLabelProp: PropTypes.string,
treeData: PropTypes.arrayOf(Object),
treeDataSimpleMode: PropTypes.oneOfType([Boolean, Object]),
loadData: PropTypes.func,
showCheckedStrategy: PropTypes.oneOf(['SHOW_ALL', 'SHOW_PARENT', 'SHOW_CHILD']),
labelInValue: PropTypes.bool,
treeCheckStrictly: PropTypes.bool,
getPopupContainer: PropTypes.func,
})

View File

@ -0,0 +1,6 @@
import '../../style/index.less'
import './index.less'
// style dependencies
import '../../select/style'
import '../../checkbox/style'

View File

@ -0,0 +1,145 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import "../../tree/style/mixin";
@import "../../checkbox/style/mixin";
@select-prefix-cls: ~"@{ant-prefix}-select";
@select-tree-prefix-cls: ~"@{ant-prefix}-select-tree";
.antCheckboxFn(@checkbox-prefix-cls: ~"@{ant-prefix}-select-tree-checkbox");
.@{select-tree-prefix-cls} {
.reset-component;
margin: 0;
padding: 0 4px;
margin-top: -4px;
li {
padding: 0;
margin: 8px 0;
list-style: none;
white-space: nowrap;
outline: 0;
&.filter-node {
> span {
font-weight: 500;
}
}
ul {
margin: 0;
padding: 0 0 0 18px;
}
.@{select-tree-prefix-cls}-node-content-wrapper {
display: inline-block;
padding: 3px 5px;
border-radius: 2px;
margin: 0;
cursor: pointer;
text-decoration: none;
color: @text-color;
transition: all .3s;
width: ~"calc(100% - 24px)";
&:hover {
background-color: @item-hover-bg;
}
&.@{select-tree-prefix-cls}-node-selected {
background-color: @primary-2;
}
}
span {
&.@{select-tree-prefix-cls}-checkbox {
margin: 0 4px 0 0;
+ .@{select-tree-prefix-cls}-node-content-wrapper {
width: ~"calc(100% - 46px)";
}
}
&.@{select-tree-prefix-cls}-switcher,
&.@{select-tree-prefix-cls}-iconEle {
margin: 0;
width: 24px;
height: 24px;
line-height: 22px;
display: inline-block;
vertical-align: middle;
border: 0 none;
cursor: pointer;
outline: none;
text-align: center;
}
&.@{select-tree-prefix-cls}-icon_loading {
&:after {
display: inline-block;
.iconfont-font("\e6ae");
animation: loadingCircle 1s infinite linear;
color: @primary-color;
}
}
&.@{select-tree-prefix-cls}-switcher {
&.@{select-tree-prefix-cls}-switcher-noop {
cursor: auto;
}
&.@{select-tree-prefix-cls}-switcher_open {
.antTreeSwitcherIcon();
}
&.@{select-tree-prefix-cls}-switcher_close {
.antTreeSwitcherIcon();
&:after {
transform: rotate(270deg) scale(0.59);
}
}
}
}
}
&-child-tree {
display: none;
&-open {
display: block;
}
}
li&-treenode-disabled {
> span:not(.@{select-tree-prefix-cls}-switcher),
> .@{select-tree-prefix-cls}-node-content-wrapper,
> .@{select-tree-prefix-cls}-node-content-wrapper span {
color: @disabled-color;
cursor: not-allowed;
}
> .@{select-tree-prefix-cls}-node-content-wrapper:hover {
background: transparent;
}
}
&-icon__open {
margin-right: 2px;
vertical-align: top;
}
&-icon__close {
margin-right: 2px;
vertical-align: top;
}
}
.@{select-prefix-cls}-tree-dropdown {
.reset-component;
.@{select-prefix-cls}-dropdown-search {
display: block;
padding: 4px;
.@{select-prefix-cls}-search__field__wrap {
width: 100%;
}
.@{select-prefix-cls}-search__field {
padding: 4px 7px;
width: 100%;
box-sizing: border-box;
border: @border-width-base @border-style-base @border-color-base;
border-radius: 4px;
outline: none;
}
&.@{select-prefix-cls}-search--hide {
display: none;
}
}
.@{select-prefix-cls}-not-found {
cursor: not-allowed;
color: @disabled-color;
padding: 7px 16px;
display: block;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,2 @@
@import "./select.less";
@import "./tree.less";

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 B

View File

@ -0,0 +1,500 @@
@selectPrefixCls: rc-tree-select;
.effect() {
animation-duration: .3s;
animation-fill-mode: both;
transform-origin: 0 0;
}
.@{selectPrefixCls} {
box-sizing: border-box;
display: inline-block;
position: relative;
vertical-align: middle;
color: #666;
&-allow-clear {
.@{selectPrefixCls}-selection--single .@{selectPrefixCls}-selection__rendered {
padding-right: 40px;
}
}
ul, li {
margin: 0;
padding: 0;
list-style: none;
}
> ul > li > a {
padding: 0;
background-color: #fff;
}
// arrow
&-arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
b {
border-color: #999999 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
width: 0;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
left: 50%;
}
}
&-selection {
outline: none;
user-select: none;
-webkit-user-select: none;
box-sizing: border-box;
display: block;
background-color: #fff;
border-radius: 6px;
border: 1px solid #d9d9d9;
&__clear {
font-weight: bold;
position: absolute;
&:after {
content: '×'
}
}
}
&-enabled &-selection {
&:hover {
border-color: #23c0fa;
box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
}
&:active {
border-color: #2db7f5;
}
}
&-selection--single {
height: 28px;
cursor: pointer;
position: relative;
.@{selectPrefixCls}-selection__rendered {
display: block;
padding-left: 10px;
padding-right: 20px;
line-height: 28px;
}
.@{selectPrefixCls}-selection-selected-value {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.@{selectPrefixCls}-selection__clear {
top: 5px;
right: 20px;
}
}
&-disabled {
color: #ccc;
cursor: not-allowed;
.@{selectPrefixCls}-selection--single,
.@{selectPrefixCls}-selection__choice__remove {
cursor: not-allowed;
color: #ccc;
&:hover {
cursor: not-allowed;
color: #ccc;
}
}
}
&-search__field__wrap {
display: inline-block;
position: relative;
}
&-search__field__placeholder {
position: absolute;
top: 0;
left: 3px;
color: #aaa;
}
&-search__field__mirror {
position: absolute;
top: 0;
left: -9999px;
white-space: pre;
pointer-events: none;
}
&-search--inline {
float: left;
width: 100%;
.@{selectPrefixCls}-search__field__wrap {
width: 100%;
}
.@{selectPrefixCls}-search__field {
border: none;
font-size: 100%;
//margin-top: 5px;
background: transparent;
outline: 0;
width: 100%;
}
> i {
float: right;
}
}
&-enabled&-selection--multiple {
cursor: text;
}
&-selection--multiple {
min-height: 28px;
.@{selectPrefixCls}-search--inline {
width: auto;
.@{selectPrefixCls}-search__field {
width: 0.75em;
}
}
.@{selectPrefixCls}-search__field__placeholder {
top: 5px;
left: 8px;
}
.@{selectPrefixCls}-selection__rendered {
//display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
padding-left: 8px;
padding-bottom: 2px;
padding-right: 10px;
}
> ul > li {
margin-top: 4px;
height: 20px;
line-height: 20px;
}
.@{selectPrefixCls}-selection__clear {
top: 5px;
right: 8px;
}
}
&-enabled {
.@{selectPrefixCls}-selection__choice {
cursor: default;
&:hover {
.@{selectPrefixCls}-selection__choice__remove {
opacity: 1;
transform: scale(1);
}
.@{selectPrefixCls}-selection__choice__remove +
.@{selectPrefixCls}-selection__choice__content {
margin-left: -8px;
margin-right: 8px;
}
}
}
}
& &-selection__choice {
background-color: #f3f3f3;
border-radius: 4px;
float: left;
padding: 0 15px;
margin-right: 4px;
position: relative;
overflow: hidden;
transition: padding .3s cubic-bezier(0.6, -0.28, 0.735, 0.045), width .3s cubic-bezier(0.6, -0.28, 0.735, 0.045);
&__content {
margin-left: 0;
margin-right: 0;
transition: margin .3s cubic-bezier(0.165, 0.84, 0.44, 1);
}
&-zoom-enter, &-zoom-appear, &-zoom-leave {
.effect();
opacity: 0;
animation-play-state: paused;
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
&-zoom-leave {
opacity: 1;
animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045);
}
&-zoom-enter.@{selectPrefixCls}-selection__choice-zoom-enter-active,
&-zoom-appear.@{selectPrefixCls}-selection__choice-zoom-appear-active {
animation-play-state: running;
animation-name: rcSelectChoiceZoomIn;
}
&-zoom-leave.@{selectPrefixCls}-selection__choice-zoom-leave-active {
animation-play-state: running;
animation-name: rcSelectChoiceZoomOut;
}
@keyframes rcSelectChoiceZoomIn {
0% {
transform: scale(0.6);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes rcSelectChoiceZoomOut {
to {
transform: scale(0);
opacity: 0;
}
}
&__remove {
color: #919191;
cursor: pointer;
font-weight: bold;
padding: 0 0 0 8px;
position: absolute;
opacity: 0;
transform: scale(0);
top: 0;
right: 2px;
transition: opacity .3s, transform .3s;
&:before {
content: '×'
}
&:hover {
color: #333;
}
}
}
&-dropdown {
background-color: white;
border: 1px solid #d9d9d9;
box-shadow: 0 0px 4px #d9d9d9;
border-radius: 4px;
box-sizing: border-box;
z-index: 100;
left: -9999px;
top: -9999px;
//border-top: none;
//border-top-left-radius: 0;
//border-top-right-radius: 0;
position: absolute;
outline: none;
&-hidden {
display: none;
}
&-menu {
outline: none;
margin: 0;
padding: 0;
list-style: none;
z-index: 9999;
> li {
margin: 0;
padding: 0;
}
&-item-group-list {
margin: 0;
padding: 0;
> li.@{selectPrefixCls}-menu-item {
padding-left: 20px;
}
}
&-item-group-title {
color: #999;
line-height: 1.5;
padding: 8px 10px;
border-bottom: 1px solid #dedede;
}
li&-item {
margin: 0;
position: relative;
display: block;
padding: 7px 10px;
font-weight: normal;
color: #666666;
white-space: nowrap;
&-selected {
background-color: #ddd;
}
&-active {
background-color: #5897fb;
color: white;
cursor: pointer;
}
&-disabled {
color: #ccc;
cursor: not-allowed;
}
&-divider {
height: 1px;
margin: 1px 0;
overflow: hidden;
background-color: #e5e5e5;
line-height: 0;
}
}
}
&-slide-up-enter, &-slide-up-appear {
.effect();
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
&-slide-up-leave {
.effect();
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
&-slide-up-enter&-slide-up-enter-active&-placement-bottomLeft, &-slide-up-appear&-slide-up-appear-active&-placement-bottomLeft {
animation-name: rcSelectDropdownSlideUpIn;
animation-play-state: running;
}
&-slide-up-leave&-slide-up-leave-active&-placement-bottomLeft {
animation-name: rcSelectDropdownSlideUpOut;
animation-play-state: running;
}
&-slide-up-enter&-slide-up-enter-active&-placement-topLeft, &-slide-up-appear&-slide-up-appear-active&-placement-topLeft {
animation-name: rcSelectDropdownSlideDownIn;
animation-play-state: running;
}
&-slide-up-leave&-slide-up-leave-active&-placement-topLeft {
animation-name: rcSelectDropdownSlideDownOut;
animation-play-state: running;
}
@keyframes rcSelectDropdownSlideUpIn {
0% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
100% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
}
@keyframes rcSelectDropdownSlideUpOut {
0% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
}
@keyframes rcSelectDropdownSlideDownIn {
0% {
opacity: 0;
transform-origin: 0% 100%;
transform: scaleY(0);
}
100% {
opacity: 1;
transform-origin: 0% 100%;
transform: scaleY(1);
}
}
@keyframes rcSelectDropdownSlideDownOut {
0% {
opacity: 1;
transform-origin: 0% 100%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 100%;
transform: scaleY(0);
}
}
}
&-dropdown-search {
display: block;
padding: 4px;
.@{selectPrefixCls}-search__field__wrap {
width: 100%;
}
.@{selectPrefixCls}-search__field__placeholder {
top: 4px;
}
.@{selectPrefixCls}-search__field {
padding: 4px;
width: 100%;
box-sizing: border-box;
border: 1px solid #d9d9d9;
border-radius: 4px;
outline: none;
}
&.@{selectPrefixCls}-search--hide {
display: none;
}
}
&-open {
.@{selectPrefixCls}-arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px;
}
}
&-not-found {
display: inline-block;
padding: 8px;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,347 @@
/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
import '../assets/index.less'
import './demo.less'
import '../../vc-dialog/assets/index.less'
import Dialog from '../../vc-dialog'
import TreeSelect, { TreeNode, SHOW_PARENT } from '../index'
import { gData } from './util'
function isLeaf (value) {
if (!value) {
return false
}
let queues = [...gData]
while (queues.length) { // BFS
const item = queues.shift()
if (item.value === value) {
if (!item.children) {
return true
}
return false
}
if (item.children) {
queues = queues.concat(item.children)
}
}
return false
}
function findPath (value, data) {
const sel = []
function loop (selected, children) {
for (let i = 0; i < children.length; i++) {
const item = children[i]
if (selected === item.value) {
sel.push(item)
return
}
if (item.children) {
loop(selected, item.children, item)
if (sel.length) {
sel.push(item)
return
}
}
}
}
loop(value, data)
return sel
}
export default {
data () {
return {
tsOpen: false,
visible: false,
inputValue: '0-0-0-label',
value: '0-0-0-value1',
// value: ['0-0-0-0-value', '0-0-0-1-value', '0-0-0-2-value'],
lv: { value: '0-0-0-value', label: 'spe label' },
multipleValue: [],
simpleTreeData: [
{ key: 1, pId: 0, label: 'test1', value: 'test1' },
{ key: 121, pId: 0, label: 'test1', value: 'test121' },
{ key: 11, pId: 1, label: 'test11', value: 'test11' },
{ key: 12, pId: 1, label: 'test12', value: 'test12' },
{ key: 111, pId: 11, label: 'test111', value: 'test111' },
],
treeDataSimpleMode: {
id: 'key',
rootPId: 0,
},
}
},
mounted () {
// console.log(this.refs.mul.getInputDOMNode());
// this.refs.mul.getInputDOMNode().setAttribute('disabled', true);
},
methods: {
onClick () {
this.visible = true
},
onClose () {
this.visible = false
},
onSearch (value) {
console.log(value, arguments)
},
onChange (value) {
console.log('onChange', arguments)
this.value = value
},
onChangeChildren (value) {
console.log('onChangeChildren', arguments)
const pre = value ? this.value : undefined
this.value = isLeaf(value) ? value : pre
},
onChangeLV (value) {
console.log('labelInValue', arguments)
if (!value) {
this.lv = undefined
return
}
const path = findPath(value.value, gData).map(i => i.label).reverse().join(' > ')
this.lv = { value: value.value, label: path }
},
onMultipleChange (value) {
console.log('onMultipleChange', arguments)
this.multipleValue = value
},
onSelect () {
// use onChange instead
console.log(...arguments)
},
onDropdownVisibleChange (visible, info) {
console.log(visible, this.value, info)
if (Array.isArray(this.value) && this.value.length > 1 &&
this.value.length < 3) {
alert('please select more than two item or less than one item.')
return false
}
return true
},
filterTreeNode (input, child) {
return String(child.title).indexOf(input) === 0
},
},
render () {
return (
<div style={{ margin: '20px' }}>
<h2>tree-select in dialog</h2>
<button class='btn btn-primary' onClick={this.onClick}>show dialog</button>
{this.visible ? <Dialog
visible={this.visible}
animation='zoom'
maskAnimation='fade'
onClose={this.onClose}
style={{ width: '600px', height: '400px', overflow: 'auto' }}
id='area'
>
<div style={{ height: '600px', paddingTop: '100px' }}>
<TreeSelect
getPopupContainer={(triggerNode) => triggerNode.parentNode}
style={{ width: '300px' }}
transitionName='rc-tree-select-dropdown-slide-up'
choiceTransitionName='rc-tree-select-selection__choice-zoom'
dropdownStyle={{ maxHeight: '200px', overflow: 'auto', zIndex: 1500 }}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
showSearch allowClear treeLine
value={this.value}
treeData={gData}
treeNodeFilterProp='label'
filterTreeNode={false}
onSearch={this.onSearch}
onChange={this.onChange}
onSelect={this.onSelect}
/>
</div>
</Dialog> : null}
<h2>single select</h2>
<TreeSelect
style={{ width: '300px' }}
transitionName='rc-tree-select-dropdown-slide-up'
choiceTransitionName='rc-tree-select-selection__choice-zoom'
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
showSearch allowClear treeLine
inputValue={this.inputValue}
value={this.value}
treeData={gData}
treeNodeFilterProp='label'
filterTreeNode={false}
onSearch={this.onSearch}
open={this.tsOpen}
onChange={(value) => {
console.log('onChange', value, arguments)
if (value === '0-0-0-0-value') {
this.tsOpen = true
} else {
this.tsOpen = false
}
this.value = value
} }
dropdownVisibleChange={(v, info) => {
console.log('single dropdownVisibleChange', v, info)
// document clicked
if (info.documentClickClose && this.value === '0-0-0-0-value') {
return false
}
return true
} }
onSelect={this.onSelect}
/>
<h2>single select (just select children)</h2>
<TreeSelect
style={{ width: '300px' }}
transitionName='rc-tree-select-dropdown-slide-up'
choiceTransitionName='rc-tree-select-selection__choice-zoom'
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
showSearch allowClear treeLine
value={this.value}
treeData={gData}
treeNodeFilterProp='label'
filterTreeNode={false}
onChange={this.onChangeChildren}
/>
<h2>multiple select</h2>
<TreeSelect ref='mul'
style={{ width: '300px' }}
transitionName='rc-tree-select-dropdown-slide-up'
choiceTransitionName='rc-tree-select-selection__choice-zoom'
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
multiple
value={this.multipleValue}
treeData={gData}
treeNodeFilterProp='title'
onChange={this.onMultipleChange}
onSelect={this.onSelect}
allowClear
/>
<h2>check select</h2>
<TreeSelect
class='check-select'
transitionName='rc-tree-select-dropdown-slide-up'
choiceTransitionName='rc-tree-select-selection__choice-zoom'
dropdownStyle={{ height: '200px', overflow: 'auto' }}
dropdownPopupAlign={{ overflow: { adjustY: 0, adjustX: 0 }, offset: [0, 2] }}
onDropdownVisibleChange={this.onDropdownVisibleChange}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
treeLine maxTagTextLength={10}
value={this.value}
inputValue={null}
treeData={gData}
treeNodeFilterProp='title'
treeCheckable showCheckedStrategy={SHOW_PARENT}
onChange={this.onChange}
onSelect={this.onSelect}
/>
<h2>labelInValue & show path</h2>
<TreeSelect
style={{ width: '500px' }}
transitionName='rc-tree-select-dropdown-slide-up'
choiceTransitionName='rc-tree-select-selection__choice-zoom'
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
showSearch allowClear treeLine
value={this.lv} labelInValue
treeData={gData}
treeNodeFilterProp='label'
filterTreeNode={false}
onChange={this.onChangeLV}
/>
<h2>use treeDataSimpleMode</h2>
<TreeSelect
style={{ width: '300px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
treeLine maxTagTextLength={10}
inputValue={'test111'}
value={this.value}
treeData={this.simpleTreeData}
treeNodeFilterProp='title'
treeDataSimpleMode={this.treeDataSimpleMode}
treeCheckable showCheckedStrategy={SHOW_PARENT}
onChange={this.onChange}
onSelect={this.onSelect}
/>
<h2>Testing in extreme conditions (Boundary conditions test) </h2>
<TreeSelect
style={{ width: '200px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
defaultValue={'leaf1'} multiple treeCheckable showCheckedStrategy={SHOW_PARENT}
treeDefaultExpandAll
treeData={[
{ key: '', value: '', label: 'empty value', children: [] },
{
key: '0', value: '0', label: '0 label', children: [
{ key: '00', value: '00', label: '00 label', children: [] },
{ key: '01', value: '01', label: '01 label', children: [] },
],
},
]}
onChange={(val) => console.log(val, arguments)}
/>
<h2>use TreeNode Component (not recommend)</h2>
<TreeSelect
style={{ width: '200px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
defaultValue={'leaf1'}
treeDefaultExpandAll
treeNodeFilterProp='title'
filterTreeNode={this.filterTreeNode}
onChange={(val) => console.log(val, arguments)}
>
<TreeNode value='' title='parent 1' key=''>
<TreeNode value='parent 1-0' title='parent 1-0' key='0-1-0'>
<TreeNode value='leaf1' title='my leaf' key='random' />
<TreeNode value='leaf2' title='your leaf' key='random1' disabled />
</TreeNode>
<TreeNode value='parent 1-1' title='parent 1-1' key='0-1-1'>
<TreeNode value='sss'
title={<span style={{ color: 'red' }}>sss</span>} key='random3'
/>
<TreeNode value='same value1' title='same txtle' key='0-1-1-1'>
<TreeNode value='same value10' title='same titlexd' key='0-1-1-1-0' />
</TreeNode>
</TreeNode>
</TreeNode>
<TreeNode value='same value2' title='same title' key='0-2'>
<TreeNode value='2same value' title='2same title' key='0-2-0' />
</TreeNode>
<TreeNode value='same value3' title='same title' key='0-3' />
</TreeSelect>
</div>
)
},
}

View File

@ -0,0 +1,63 @@
import PropTypes from '../../_util/vue-types'
import { generateData, calcTotal } from './util'
const Gen = {
props: {
x: PropTypes.number.def(20),
y: PropTypes.number.def(18),
z: PropTypes.number.def(1),
},
data () {
return {
nums: '',
}
},
mounted () {
this.$refs.x.value = this.x
this.$refs.y.value = this.y
this.$refs.z.value = this.z
const vals = this.getVals()
this.$emit('gen', generateData(vals.x, vals.y, vals.z))
},
methods: {
onGen (e) {
e.preventDefault()
const vals = this.getVals()
this.$emit('gen', generateData(vals.x, vals.y, vals.z))
this.nums = calcTotal(vals.x, vals.y, vals.z)
},
getVals () {
return {
x: parseInt(this.$refs.x.value, 10),
y: parseInt(this.$refs.y.value, 10),
z: parseInt(this.$refs.z.value, 10),
}
},
},
render () {
const { x, y, z } = this
return (<div style={{ padding: '0 20px' }}>
<h2>big data generator</h2>
<form onSubmit={this.onGen}>
<span style={{ marginRight: '10px' }}>
x: <input ref='x' type='number' min='1' required style={{ width: '50px' }} />
</span>
<span style={{ marginRight: '10px' }}>
y: <input ref='y' type='number' min='1' required style={{ width: '50px' }} />
</span>
<span style={{ marginRight: '10px' }}>
z: <input ref='z' type='number' min='1' required style={{ width: '50px' }} />
</span>
<button type='submit'>Generate</button>
<p>total nodes: {this.nums || calcTotal(x, y, z)}</p>
</form>
<p style={{ fontSize: '12px' }}>
x每一级下的节点总数y每级节点里有y个节点存在子节点z树的level层级数0表示一级
</p>
</div>)
},
}
export default Gen

View File

@ -0,0 +1,78 @@
/* eslint react/no-multi-comp:0, no-console:0 */
import '../assets/index.less'
import './demo.less'
import TreeSelect, { SHOW_PARENT } from '../index'
import Gen from './big-data-generator'
export default {
data () {
return {
gData: [],
gData1: [],
value: '',
value1: '',
}
},
methods: {
onChange (value) {
console.log('onChange', arguments)
this.value = value
},
onChangeStrictly (value1) {
console.log('onChangeStrictly', arguments)
const ind = parseInt(Math.random() * 3, 10)
value1.push({ value: `0-0-0-${ind}-value`, label: `0-0-0-${ind}-label`, halfChecked: true })
this.value1 = value1
},
onGen (data) {
Object.assign(this.$data, {
gData: data,
gData1: [...data],
value: '0-0-0-value',
value1: [
{ value: '0-0-value', label: '0-0-label', halfChecked: true },
{ value: '0-0-0-value', label: '0-0-0-label' },
],
// value: ['0-0-0-0-value', '0-0-0-1-value', '0-0-0-2-value'],
})
},
},
render () {
return (<div style={{ padding: '0 20px' }}>
<Gen onGen={this.onGen} />
<div style={{ display: 'flex' }}>
<div style={{ marginRight: '20px' }}>
<h3>normal check</h3>
<TreeSelect
style={{ width: '300px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
treeData={this.gData} treeLine
value={this.value}
placeholder={<i>请下拉选择</i>}
treeCheckable
showCheckedStrategy={SHOW_PARENT}
onChange={this.onChange}
/>
</div>
<div>
<h3>checkStrictly</h3>
<TreeSelect
style={{ width: '300px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
treeData={this.gData1} treeLine
value={this.value1}
placeholder={<i>请下拉选择</i>}
treeCheckable
treeCheckStrictly
showCheckedStrategy={SHOW_PARENT}
onChange={this.onChangeStrictly}
/>
</div>
</div>
</div>)
},
}

View File

@ -0,0 +1,15 @@
.rc-tree-select-selection--multiple {
max-height: 50px;
overflow-y: scroll;
}
.rc-tree-select-dropdown {
max-height: 350px;
overflow-y: scroll;
}
.check-select {
width: 300px;
.rc-tree-select-selection--multiple {
min-height: 50px;
}
}

View File

@ -0,0 +1,80 @@
/* eslint react/no-multi-comp:0, no-console:0 */
import '../assets/index.less'
import TreeSelect from '../index'
const SHOW_PARENT = TreeSelect.SHOW_PARENT
const treeData = [{
label: 'Node1',
value: '0-0',
key: '0-0',
children: [{
label: 'Child Node1',
value: '0-0-0',
key: '0-0-0',
}],
}, {
label: 'Node2',
value: '0-1',
key: '0-1',
children: [{
label: 'Child Node3',
value: '0-1-0',
key: '0-1-0',
}, {
label: 'Child Node4',
value: '0-1-1',
key: '0-1-1',
}, {
label: 'Child Node5',
value: '0-1-2',
key: '0-1-2',
}],
}]
export default {
data () {
return {
value: ['0-0-0'],
disabled: false,
}
},
methods: {
onChange (value) {
console.log('onChange ', value, arguments)
this.value = value
},
switch (checked) {
this.disabled = checked
},
},
render () {
const tProps = {
props: {
treeData,
disabled: this.disabled,
value: this.value,
multiple: true,
allowClear: true,
treeCheckable: true,
showCheckedStrategy: SHOW_PARENT,
searchPlaceholder: 'Please select',
},
on: {
change: this.onChange,
},
style: {
width: '300px',
},
}
return (
<div>
<TreeSelect {...tProps} />
<input type='checkbox' onChange={e => this.switch(e.target.checked)}/> 禁用
</div>
)
},
}

View File

@ -0,0 +1,55 @@
/* eslint react/no-multi-comp:0, no-console:0 */
import '../assets/index.less'
import TreeSelect from '../index'
import { getNewTreeData, generateTreeNodes } from './util'
export default {
data () {
return {
treeData: [
{ label: 'pNode 01', value: '0-0', key: '0-0' },
{ label: 'pNode 02', value: '0-1', key: '0-1' },
{ label: 'pNode 03', value: '0-2', key: '0-2', isLeaf: true },
],
// value: '0-0',
value: { value: '0-0-0-value', label: '0-0-0-label' },
}
},
methods: {
onChange (value) {
console.log(value)
this.value = value
},
onLoadData (treeNode) {
console.log(treeNode)
return new Promise((resolve) => {
setTimeout(() => {
const treeData = [...this.treeData]
getNewTreeData(treeData, treeNode.eventKey, generateTreeNodes(treeNode), 2)
this.treeData = treeData
resolve()
}, 500)
})
},
},
render () {
return (
<div style={{ padding: '10px 30px' }}>
<h2>dynamic render</h2>
<TreeSelect
style={{ width: '300px' }}
treeData={this.treeData}
labelInValue
value={this.value}
onChange={this.onChange}
loadData={this.onLoadData}
/>
</div>
)
},
}

View File

@ -0,0 +1,98 @@
/* eslint react/no-multi-comp:0, no-console:0 */
import '../assets/index.less'
import TreeSelect, { SHOW_PARENT } from '../index'
import { gData } from './util'
export default {
data () {
return {
value: '11',
// value: ['0-0-0-0-value', '0-0-0-1-value', '0-0-0-2-value'],
simpleTreeData: [
{ key: 1, pId: 0, label: 'a', value: 'a' },
{ key: 11, pId: 1, label: 'a12', value: 'a12', disabled: true },
{ key: 111, pId: 11, label: 'a00', value: 'a00', selectable: false },
{ key: 2, pId: 0, label: 'b', value: 'b' },
{ key: 20, pId: 2, label: 'b10', value: 'b10' },
{ key: 21, pId: 2, label: 'b1', value: 'b1' },
{ key: 22, pId: 2, label: 'b12', value: 'b12' },
],
treeDataSimpleMode: {
id: 'key',
rootPId: 0,
},
}
},
methods: {
onChange (value) {
if (value.length === 1) {
// return;
}
console.log('onChange', arguments, this.simpleTreeData)
this.value = value
},
onSelect () {
// use onChange instead
// console.log(arguments);
},
onDataChange () {
const data = [...this.simpleTreeData]
data.forEach(i => {
if (i.key === 11) {
delete i.disabled
}
if (i.key === 20) {
i.disabled = true
}
})
this.simpleTreeData = data
},
},
render () {
return (
<div style={{ margin: '20px' }}>
<h2>check select</h2>
<TreeSelect
style={{ width: '300px' }}
transitionName='rc-tree-select-dropdown-slide-up'
choiceTransitionName='rc-tree-select-selection__choice-zoom'
dropdownStyle={{ height: '200px', overflow: 'auto' }}
dropdownPopupAlign={{ overflow: { adjustY: 0, adjustX: 0 }, offset: [0, 2] }}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
treeLine maxTagTextLength={10}
value={this.value}
treeData={gData}
treeNodeFilterProp='title'
treeCheckable
onChange={this.onChange}
onSelect={this.onSelect}
/>
<h2>use treeDataSimpleMode</h2>
<TreeSelect
style={{ width: '300px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
treeLine maxTagTextLength={10}
inputValue={null}
value={this.value}
treeData={this.simpleTreeData}
treeDefaultExpandAll
treeNodeFilterProp='title'
treeDataSimpleMode={this.treeDataSimpleMode}
treeCheckable showCheckedStrategy={SHOW_PARENT}
onChange={this.onChange}
onSelect={this.onSelect}
/>
<button onClick={this.onDataChange}>change data</button>
</div>
)
},
}

View File

@ -0,0 +1,138 @@
import '../assets/index.less'
import TreeSelect from '../index'
import Select from '../../vc-select'
import { createForm } from '../../vc-form'
import { regionStyle, errorStyle } from './styles'
import { gData } from './util'
import '../../vc-select/assets/index.less'
import './demo.less'
const { Option } = Select
const TreeSelectInput = {
props: [
'multiple',
'treeData',
'treeCheckable',
'value',
],
methods: {
onChange (value) {
console.log(value, arguments)
this.$emit('change', value)
},
},
render () {
return (
<TreeSelect {...{ props: this.$props }} onChange={this.onChange.bind(this)} />
)
},
}
const Form = {
methods: {
onSubmit (e) {
console.log('submit')
e.preventDefault()
this.form.validateFields((error, values) => {
if (!error) {
console.log('ok', values)
} else {
console.log('error', error, values)
}
})
},
reset (e) {
e.preventDefault()
this.form.resetFields()
},
},
render () {
const { form } = this
const { getFieldDecorator, getFieldError } = form
const tProps = {
props: {
multiple: true,
treeData: gData,
treeCheckable: true,
// treeDefaultExpandAll: true,
},
}
return (<div style={{ margin: '20px' }}>
<h2>validity</h2>
<form onSubmit={this.onSubmit}>
<div style={regionStyle}>
<div>
<p style={{ color: 'blue' }}>no onChange</p>
{getFieldDecorator('tree-select', {
initialValue: ['0-0-0-value'],
rules: [
{ required: true, type: 'array', message: 'tree-select 需要必填' },
],
})(
<TreeSelect
{...tProps}
style={{ width: '300px' }}
/>
)}
</div>
<p style={errorStyle}>
{(getFieldError('tree-select')) ? getFieldError('tree-select').join(',') : null}
</p>
</div>
<div style={regionStyle}>
<div>
<p style={{ color: 'blue' }}>custom onChange</p>
{getFieldDecorator('tree-select1', {
initialValue: ['0-0-0-value'],
rules: [
{ required: true, type: 'array', message: 'tree-select1 需要必填' },
],
})(
<TreeSelectInput
{...tProps}
style={{ width: '300px' }}
// treeData={gData}
/>
)}
</div>
<p style={errorStyle}>
{(getFieldError('tree-select1')) ? getFieldError('tree-select1').join(',') : null}
</p>
</div>
<div style={regionStyle}>
{getFieldDecorator('select', {
initialValue: ['jack'],
rules: [
{ required: true, type: 'array', message: 'select 需要必填' },
],
})(
<Select
style={{ width: '200px' }} allowClear multiple
>
<Option value='jack'>jack</Option>
<Option value='lucy'>lucy</Option>
<Option value='disabled' disabled>disabled</Option>
<Option value='yiminghe'>yiminghe</Option>
</Select>
)}
<p style={errorStyle}>
{(getFieldError('select')) ? getFieldError('select').join(',') : null}
</p>
</div>
<div style={regionStyle}>
<button onClick={this.reset}>reset</button>
&nbsp;
<input type='submit' value='submit'/>
</div>
</form>
</div>)
},
}
export default createForm()(Form)

View File

@ -0,0 +1,11 @@
export const regionStyle = {
border: '1px solid red',
marginTop: '10px',
padding: '10px',
}
export const errorStyle = {
color: 'red',
marginTop: '10px',
padding: '10px',
}

View File

@ -0,0 +1,142 @@
/* eslint no-loop-func: 0, no-console: 0 */
export function generateData (x = 3, y = 2, z = 1, gData = []) {
// x每一级下的节点总数。y每级节点里有y个节点、存在子节点。z树的level层级数0表示一级
function _loop (_level, _preKey, _tns) {
const preKey = _preKey || '0'
const tns = _tns || gData
const children = []
for (let i = 0; i < x; i++) {
const key = `${preKey}-${i}`
tns.push({
label: `${key}-label`,
value: `${key}-value`,
key,
disabled: key === '0-0-0-1' || false,
})
if (i < y) {
children.push(key)
}
}
if (_level < 0) {
return tns
}
const __level = _level - 1
children.forEach((key, index) => {
tns[index].children = []
return _loop(__level, key, tns[index].children)
})
}
_loop(z)
return gData
}
export function calcTotal (x = 3, y = 2, z = 1) {
/* eslint no-param-reassign:0*/
const rec = (n) => n >= 0 ? x * Math.pow(y, n--) + rec(n) : 0
return rec(z + 1)
}
console.log('总节点数单个tree', calcTotal())
export const gData = generateData()
export function generateTreeNodes (treeNode) {
const arr = []
const key = treeNode.eventKey
for (let i = 0; i < 3; i++) {
arr.push({ label: `${key}-${i}-label`, value: `${key}-${i}-value`, key: `${key}-${i}` })
}
return arr
}
function setLeaf (treeData, curKey, level) {
const loopLeaf = (data, lev) => {
const l = lev - 1
data.forEach((item) => {
if ((item.key.length > curKey.length) ? item.key.indexOf(curKey) !== 0
: curKey.indexOf(item.key) !== 0) {
return
}
if (item.children) {
loopLeaf(item.children, l)
} else if (l < 1) {
item.isLeaf = true
}
})
}
loopLeaf(treeData, level + 1)
}
export function getNewTreeData (treeData, curKey, child, level) {
const loop = (data) => {
if (level < 1 || curKey.length - 3 > level * 2) return
data.forEach((item) => {
if (curKey.indexOf(item.key) === 0) {
if (item.children) {
loop(item.children)
} else {
item.children = child
}
}
})
}
loop(treeData)
setLeaf(treeData, curKey, level)
}
function loopData (data, callback) {
const loop = (d, level = 0) => {
d.forEach((item, index) => {
const pos = `${level}-${index}`
if (item.children) {
loop(item.children, pos)
}
callback(item, index, pos)
})
}
loop(data)
}
function isPositionPrefix (smallPos, bigPos) {
if (bigPos.length < smallPos.length) {
return false
}
// attention: "0-0-1" "0-0-10"
if ((bigPos.length > smallPos.length) && (bigPos.charAt(smallPos.length) !== '-')) {
return false
}
return bigPos.substr(0, smallPos.length) === smallPos
}
// console.log(isPositionPrefix("0-1", "0-10-1"));
export function getFilterValue (val, sVal, delVal) {
const allPos = []
const delPos = []
loopData(gData, (item, index, pos) => {
if (sVal.indexOf(item.value) > -1) {
allPos.push(pos)
}
if (delVal.indexOf(item.value) > -1) {
delPos.push(pos)
}
})
const newPos = []
delPos.forEach((item) => {
allPos.forEach((i) => {
if (isPositionPrefix(item, i) || isPositionPrefix(i, item)) {
// 过滤掉 父级节点 和 所有子节点。
// 因为 node节点 不选时,其 父级节点 和 所有子节点 都不选。
return
}
newPos.push(i)
})
})
const newVal = []
if (newPos.length) {
loopData(gData, (item, index, pos) => {
if (newPos.indexOf(pos) > -1) {
newVal.push(item.value)
}
})
}
return newVal
}

View File

@ -0,0 +1,7 @@
// rc-tree-select 1.12.13 tag
// export this package's api
import TreeSelect from './src'
export default TreeSelect
export { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './src'

View File

@ -0,0 +1,108 @@
import PropTypes from '../../_util/vue-types'
import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies'
function nonEmptyStringType (props, propsName) {
const value = props[propsName]
if (typeof value !== 'string' || !value) {
return new Error() // Just a flag, so don't need message.
}
}
function valueType (props, propName, componentName) {
const labelInValueShape = PropTypes.shape({
value: nonEmptyStringType,
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 \`labelInValue\` is \`true\`, \`${propName}\` should in ` +
`shape of \`{ value: string, label?: string }\`.`
)
}
} else if (props.treeCheckable && props.treeCheckStrictly) {
const validate = PropTypes.oneOfType([
PropTypes.arrayOf(labelInValueShape),
labelInValueShape,
])
const error = validate(...arguments)
if (error) {
return new Error(
`Invalid prop \`${propName}\` supplied to \`${componentName}\`, ` +
`when \`treeCheckable\` and \`treeCheckStrictly\` are \`true\`, ` +
`\`${propName}\` should in shape of \`{ value: string, label?: string }\`.`
)
}
} else if (props.multiple && props[propName] === '') {
return new Error(
`Invalid prop \`${propName}\` of type \`string\` supplied to \`${componentName}\`, ` +
`expected \`array\` when \`multiple\` is \`true\`.`
)
} else {
const validate = PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.string,
])
return validate(...arguments)
}
}
export const SelectPropTypes = {
// className: PropTypes.string,
prefixCls: PropTypes.string,
multiple: PropTypes.bool,
filterTreeNode: PropTypes.any,
showSearch: PropTypes.bool,
disabled: PropTypes.bool,
showArrow: PropTypes.bool,
allowClear: PropTypes.bool,
defaultOpen: PropTypes.bool,
open: PropTypes.bool,
transitionName: PropTypes.string,
animation: PropTypes.string,
choiceTransitionName: PropTypes.string,
// onClick: PropTypes.func,
// onChange: PropTypes.func,
// onSelect: PropTypes.func,
// onDeselect: PropTypes.func,
// onSearch: PropTypes.func,
searchPlaceholder: PropTypes.string,
placeholder: PropTypes.any,
inputValue: PropTypes.any,
value: PropTypes.any,
defaultValue: PropTypes.any,
label: PropTypes.any, // vnode
defaultLabel: PropTypes.any,
labelInValue: PropTypes.bool,
dropdownClassName: PropTypes.string,
dropdownStyle: PropTypes.object,
dropdownPopupAlign: PropTypes.object,
dropdownVisibleChange: PropTypes.func,
maxTagTextLength: PropTypes.number,
showCheckedStrategy: PropTypes.oneOf([
SHOW_ALL, SHOW_PARENT, SHOW_CHILD,
]),
treeCheckStrictly: PropTypes.bool,
treeIcon: PropTypes.bool,
treeLine: PropTypes.bool,
treeDefaultExpandAll: PropTypes.bool,
treeCheckable: PropTypes.any, // bool vnode
treeNodeLabelProp: PropTypes.string,
treeNodeFilterProp: PropTypes.string,
treeData: PropTypes.array,
treeDataSimpleMode: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.object,
]),
loadData: PropTypes.func,
dropdownMatchSelectWidth: PropTypes.bool,
notFoundContent: PropTypes.any,
children: PropTypes.any,
autoFocus: PropTypes.bool,
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,371 @@
import PropTypes from '../../_util/vue-types'
import classnames from 'classnames'
import omit from 'omit.js'
import Trigger from '../../trigger'
import Tree, { TreeNode } from '../../vc-tree'
import { SelectPropTypes } from './PropTypes'
import BaseMixin from '../../_util/BaseMixin'
import {
loopAllChildren,
flatToHierarchy,
getValuePropValue,
labelCompatible,
saveRef,
} from './util'
import { cloneElement } from '../../_util/vnode'
import { isEmptyElement, getSlotOptions, getKey, getAllProps, getComponentFromProp } from '../../_util/props-util'
import { noop } from '../../_util/vue-types/utils'
const BUILT_IN_PLACEMENTS = {
bottomLeft: {
points: ['tl', 'bl'],
offset: [0, 4],
overflow: {
adjustX: 0,
adjustY: 1,
},
},
topLeft: {
points: ['bl', 'tl'],
offset: [0, -4],
overflow: {
adjustX: 0,
adjustY: 1,
},
},
}
const SelectTrigger = {
mixins: [BaseMixin],
name: 'SelectTrigger',
props: {
...SelectPropTypes,
dropdownMatchSelectWidth: PropTypes.bool,
dropdownPopupAlign: PropTypes.object,
visible: PropTypes.bool,
filterTreeNode: PropTypes.any,
treeNodes: PropTypes.any,
inputValue: PropTypes.string,
prefixCls: PropTypes.string,
popupClassName: PropTypes.string,
_cachetreeData: PropTypes.any,
_treeNodesStates: PropTypes.any,
halfCheckedValues: PropTypes.any,
inputElement: PropTypes.any,
},
data () {
return {
sExpandedKeys: [],
fireOnExpand: false,
dropdownWidth: null,
}
},
mounted () {
this.$nextTick(() => {
this.setDropdownWidth()
})
},
watch: {
inputValue (val) {
// set autoExpandParent to true
this.setState({
sExpandedKeys: [],
fireOnExpand: false,
})
},
},
updated () {
this.$nextTick(() => {
this.setDropdownWidth()
})
},
methods: {
onExpand (expandedKeys) {
// rerender
this.setState({
sExpandedKeys: expandedKeys,
fireOnExpand: true,
}, () => {
// Fix https://github.com/ant-design/ant-design/issues/5689
if (this.$refs.trigger && this.$refs.trigger.forcePopupAlign) {
this.$refs.trigger.forcePopupAlign()
}
})
},
setDropdownWidth () {
const width = this.$el.offsetWidth
if (width !== this.dropdownWidth) {
this.setState({ dropdownWidth: width })
}
},
getPopupEleRefs () {
return this.$refs.popupEle
},
getPopupDOMNode () {
return this.$refs.trigger.getPopupDomNode()
},
getDropdownTransitionName () {
const props = this.$props
let transitionName = props.transitionName
if (!transitionName && props.animation) {
transitionName = `${this.getDropdownPrefixCls()}-${props.animation}`
}
return transitionName
},
getDropdownPrefixCls () {
return `${this.prefixCls}-dropdown`
},
highlightTreeNode (treeNode) {
const props = this.$props
const filterVal = treeNode.$props[labelCompatible(props.treeNodeFilterProp)]
if (typeof filterVal === 'string') {
return props.inputValue && filterVal.indexOf(props.inputValue) > -1
}
return false
},
filterTreeNode_ (input, child) {
if (!input) {
return true
}
const filterTreeNode = this.filterTreeNode
if (!filterTreeNode) {
return true
}
const props = getAllProps(child)
if (props && props.disabled) {
return false
}
return filterTreeNode.call(this, input, child)
},
processTreeNode (treeNodes) {
const filterPoss = []
this._expandedKeys = []
loopAllChildren(treeNodes, (child, index, pos) => {
if (this.filterTreeNode_(this.inputValue, child)) {
filterPoss.push(pos)
this._expandedKeys.push(String(getKey(child)))
}
})
// Include the filtered nodes's ancestral nodes.
const processedPoss = []
filterPoss.forEach(pos => {
const arr = pos.split('-')
arr.reduce((pre, cur) => {
const res = `${pre}-${cur}`
if (processedPoss.indexOf(res) < 0) {
processedPoss.push(res)
}
return res
})
})
const filterNodesPositions = []
loopAllChildren(treeNodes, (child, index, pos) => {
if (processedPoss.indexOf(pos) > -1) {
filterNodesPositions.push({ node: child, pos })
}
})
const hierarchyNodes = flatToHierarchy(filterNodesPositions)
const recursive = children => {
return children.map(child => {
if (child.children) {
return cloneElement(child.node, {
children: recursive(child.children),
})
}
return child.node
})
}
return recursive(hierarchyNodes)
},
onSelect () {
this.__emit('select', ...arguments)
},
renderTree (keys, halfCheckedKeys, newTreeNodes, multiple) {
const props = this.$props
const trProps = {
multiple,
prefixCls: `${props.prefixCls}-tree`,
showIcon: props.treeIcon,
showLine: props.treeLine,
defaultExpandAll: props.treeDefaultExpandAll,
defaultExpandedKeys: props.treeDefaultExpandedKeys,
filterTreeNode: this.highlightTreeNode,
}
const trListeners = {}
if (props.treeCheckable) {
trProps.selectable = false
trProps.checkable = props.treeCheckable
trListeners.check = this.onSelect
trProps.checkStrictly = props.treeCheckStrictly
if (props.inputValue) {
// enable checkStrictly when search tree.
trProps.checkStrictly = true
} else {
trProps._treeNodesStates = props._treeNodesStates
}
if (trProps.treeCheckStrictly && halfCheckedKeys.length) {
trProps.checkedKeys = { checked: keys, halfChecked: halfCheckedKeys }
} else {
trProps.checkedKeys = keys
}
} else {
trProps.selectedKeys = keys
trListeners.select = this.onSelect
}
// expand keys
if (!trProps.defaultExpandAll && !trProps.defaultExpandedKeys && !props.loadData) {
trProps.expandedKeys = keys
}
trProps.autoExpandParent = true
trListeners.expand = this.onExpand
if (this._expandedKeys && this._expandedKeys.length) {
trProps.expandedKeys = this._expandedKeys
}
if (this.fireOnExpand) {
trProps.expandedKeys = this.sExpandedKeys
trProps.autoExpandParent = false
}
// async loadData
if (props.loadData) {
trProps.loadData = props.loadData
}
return (
<Tree ref='popupEle' {...{ props: trProps, on: trListeners }}>
{newTreeNodes}
</Tree>
)
},
},
render () {
const props = this.$props
const multiple = props.multiple
const dropdownPrefixCls = this.getDropdownPrefixCls()
const popupClassName = {
[props.dropdownClassName]: !!props.dropdownClassName,
[`${dropdownPrefixCls}--${multiple ? 'multiple' : 'single'}`]: 1,
}
let visible = props.visible
const search = multiple || !props.showSearch ? null : (
<span class={`${dropdownPrefixCls}-search`}>{props.inputElement}</span>
)
const recursive = children => {
return children.map(function handler(child) { // eslint-disable-line
// if (isEmptyElement(child) || (child.data && child.data.slot)) {
// return null
// }
if (!getSlotOptions(child).__ANT_TREE_SELECT_NODE) {
return null
}
const treeNodeProps = {
...child.data,
props: {
...getAllProps(child),
title: getComponentFromProp(child, 'title') || getComponentFromProp(child, 'label'),
},
key: String(child.key),
}
if (child && child.componentOptions.children) {
// null or String has no Prop
return (
<TreeNode {...treeNodeProps}>
{recursive(child.componentOptions.children) }
</TreeNode>
)
}
return <TreeNode {...treeNodeProps} />
})
}
// const s = Date.now();
let treeNodes
if (props._cachetreeData && this.cacheTreeNodes) {
treeNodes = this.cacheTreeNodes
} else {
treeNodes = recursive(props.treeData || props.treeNodes)
this.cacheTreeNodes = treeNodes
}
// console.log(Date.now()-s);
if (props.inputValue) {
treeNodes = this.processTreeNode(treeNodes)
}
const keys = []
const halfCheckedKeys = []
loopAllChildren(treeNodes, (child) => {
if (props.value.some(item => item.value === getValuePropValue(child))) {
keys.push(String(getKey(child)))
}
if (props.halfCheckedValues &&
props.halfCheckedValues.some(item => item.value === getValuePropValue(child))) {
halfCheckedKeys.push(String(getKey(child)))
}
})
let notFoundContent
if (!treeNodes.length) {
if (props.notFoundContent) {
notFoundContent = (
<span class={`${props.prefixCls}-not-found`}>
{props.notFoundContent}
</span>
)
} else if (!search) {
visible = false
}
}
const popupElement = (
<div>
{search}
{notFoundContent || this.renderTree(keys, halfCheckedKeys, treeNodes, multiple)}
</div>
)
const popupStyle = { ...props.dropdownStyle }
const widthProp = props.dropdownMatchSelectWidth ? 'width' : 'minWidth'
if (this.dropdownWidth) {
popupStyle[widthProp] = `${this.dropdownWidth}px`
}
return (
<Trigger
action={props.disabled ? [] : ['click']}
ref='trigger'
popupPlacement='bottomLeft'
builtinPlacements={BUILT_IN_PLACEMENTS}
popupAlign={props.dropdownPopupAlign}
prefixCls={dropdownPrefixCls}
popupTransitionName={this.getDropdownTransitionName()}
onPopupVisibleChange={props.dropdownVisibleChange}
popup={popupElement}
popupVisible={visible}
getPopupContainer={props.getPopupContainer}
popupClassName={classnames(popupClassName)}
popupStyle={popupStyle}
>
{this.$slots.default}
</Trigger>
)
},
}
export default SelectTrigger

View File

@ -0,0 +1,12 @@
import { TreeNode } from '../../vc-tree'
export default {
name: 'TreeNode',
__ANT_TREE_SELECT_NODE: true,
props: {
...TreeNode.props,
value: String,
},
render () {
return this
},
}

View File

@ -0,0 +1,26 @@
// export this package's api
import TreeSelect from './Select'
import TreeNode from './TreeNode'
import omit from 'omit.js'
import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies'
TreeSelect.TreeNode = TreeNode
export default {
functional: true,
render (h, context) {
const { props, listeners, children = [], data } = context
const treeSelectProps = {
...omit(data, ['attrs']),
props: {
...props,
children,
__propsSymbol__: Symbol(),
},
on: listeners,
}
return <TreeSelect {...treeSelectProps}/>
},
TreeNode,
SHOW_ALL, SHOW_PARENT, SHOW_CHILD,
}
export { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD }

View File

@ -0,0 +1,3 @@
export const SHOW_ALL = 'SHOW_ALL'
export const SHOW_PARENT = 'SHOW_PARENT'
export const SHOW_CHILD = 'SHOW_CHILD'

View File

@ -0,0 +1,569 @@
import { getPropsData, getAllProps, getKey, getAttrs, getSlotOptions, filterEmpty, getSlots } from '../../_util/props-util'
import { cloneVNodes, cloneElement } from '../../_util/vnode'
export function toTitle (title) {
if (typeof title === 'string') {
return title
}
return null
}
export function getValuePropValue (child) {
const props = getAllProps(child)
if ('value' in props) {
return props.value
}
if (getKey(child) !== undefined) {
return getKey(child)
}
throw new Error(`no key or value for ${child}`)
}
export function getPropValue (child, prop) {
if (prop === 'value') {
return getValuePropValue(child)
}
const slots = getSlots(child)
if (prop === 'children') {
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
}
if (slots[prop]) {
return cloneVNodes(slots[prop], true)
}
const data = getPropsData(child)
if (prop in data) {
return data[prop]
} else {
return getAttrs(child)[prop]
}
}
export function isMultiple (props) {
return !!(props.multiple || props.treeCheckable)
}
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 const UNSELECTABLE_STYLE = {
userSelect: 'none',
WebkitUserSelect: 'none',
}
export const UNSELECTABLE_ATTRIBUTE = {
unselectable: 'unselectable',
}
export function labelCompatible (prop) {
let newProp = prop
if (newProp === 'label') {
newProp = 'title'
}
return newProp
}
export function isInclude (smallArray, bigArray) {
// attention: [0,0,1] [0,0,10]
return smallArray.every((ii, i) => {
return ii === bigArray[i]
})
}
export function isPositionPrefix (smallPos, bigPos) {
if (!bigPos || !smallPos) {
// console.log(smallPos, bigPos);
return false
}
if (bigPos.length < smallPos.length) {
return false
}
// attention: "0-0-1" "0-0-10"
if ((bigPos.length > smallPos.length) && (bigPos.charAt(smallPos.length) !== '-')) {
return false
}
return bigPos.substr(0, smallPos.length) === smallPos
}
/*
export function getCheckedKeys(node, checkedKeys, allCheckedNodesKeys) {
const nodeKey = node.props.eventKey;
let newCks = [...checkedKeys];
let nodePos;
const unCheck = allCheckedNodesKeys.some(item => {
if (item.key === nodeKey) {
nodePos = item.pos;
return true;
}
});
if (unCheck) {
newCks = [];
allCheckedNodesKeys.forEach(item => {
if (isPositionPrefix(item.pos, nodePos) || isPositionPrefix(nodePos, item.pos)) {
return;
}
newCks.push(item.key);
});
} else {
newCks.push(nodeKey);
}
return newCks;
}
*/
function getChildrenlength (children) {
let len = 1
if (Array.isArray(children)) {
len = children.length
}
return len
}
function getSiblingPosition (index, len, siblingPosition) {
if (len === 1) {
siblingPosition.first = true
siblingPosition.last = true
} else {
siblingPosition.first = index === 0
siblingPosition.last = index === len - 1
}
return siblingPosition
}
function filterChild (childs) {
const newChilds = []
childs.forEach(child => {
const options = getSlotOptions(child)
if (options.__ANT_TREE_NODE || options.__ANT_TREE_SELECT_NODE) {
newChilds.push(child)
}
})
return newChilds
}
export function loopAllChildren (childs, callback, parent) {
const loop = (children, level, _parent) => {
const len = getChildrenlength(children)
children.forEach(function handler(item, index) { // eslint-disable-line
const pos = `${level}-${index}`
if (item && item.componentOptions && item.componentOptions.children) {
loop(filterChild(item.componentOptions.children), pos, { node: item, pos })
}
if (item) {
callback(item, index, pos, item.key || pos, getSiblingPosition(index, len, {}), _parent)
}
})
}
loop(filterChild(childs), 0, parent)
}
// export function loopAllChildren(childs, callback) {
// const loop = (children, level) => {
// React.Children.forEach(children, (item, index) => {
// const pos = `${level}-${index}`;
// if (item && item.props.children) {
// loop(item.props.children, pos);
// }
// if (item) {
// callback(item, index, pos, getValuePropValue(item));
// }
// });
// };
// loop(childs, 0);
// }
// TODO: Here has the side effect. Update node children data affect.
export function flatToHierarchy (arr) {
if (!arr.length) {
return arr
}
const hierarchyNodes = []
const levelObj = {}
arr.forEach((item) => {
if (!item.pos) {
return
}
const posLen = item.pos.split('-').length
if (!levelObj[posLen]) {
levelObj[posLen] = []
}
levelObj[posLen].push(item)
})
const levelArr = Object.keys(levelObj).sort((a, b) => b - a)
// const s = Date.now();
// todo: there are performance issues!
levelArr.reduce((pre, cur) => {
if (cur && cur !== pre) {
levelObj[pre].forEach((item) => {
let haveParent = false
levelObj[cur].forEach((ii) => {
if (isPositionPrefix(ii.pos, item.pos)) {
haveParent = true
if (!ii.children) {
ii.children = []
}
ii.children.push(item)
}
})
if (!haveParent) {
hierarchyNodes.push(item)
}
})
}
return cur
})
// console.log(Date.now() - s);
return levelObj[levelArr[levelArr.length - 1]].concat(hierarchyNodes)
}
// arr.length === 628, use time: ~20ms
export function filterParentPosition (arr) {
const levelObj = {}
arr.forEach((item) => {
const posLen = item.split('-').length
if (!levelObj[posLen]) {
levelObj[posLen] = []
}
levelObj[posLen].push(item)
})
const levelArr = Object.keys(levelObj).sort()
for (let i = 0; i < levelArr.length; i++) {
if (levelArr[i + 1]) {
levelObj[levelArr[i]].forEach(ii => {
for (let j = i + 1; j < levelArr.length; j++) {
levelObj[levelArr[j]].forEach((_i, index) => {
if (isPositionPrefix(ii, _i)) {
levelObj[levelArr[j]][index] = null
}
})
levelObj[levelArr[j]] = levelObj[levelArr[j]].filter(p => p)
}
})
}
}
let nArr = []
levelArr.forEach(i => {
nArr = nArr.concat(levelObj[i])
})
return nArr
}
// console.log(filterParentPosition(
// ['0-2', '0-3-3', '0-10', '0-10-0', '0-0-1', '0-0', '0-1-1', '0-1']
// ));
function stripTail (str) {
const arr = str.match(/(.+)(-[^-]+)$/)
let st = ''
if (arr && arr.length === 3) {
st = arr[1]
}
return st
}
function splitPosition (pos) {
return pos.split('-')
}
// todo: do optimization.
export function handleCheckState (obj, checkedPositionArr, checkIt) {
// console.log(stripTail('0-101-000'));
// let s = Date.now();
let objKeys = Object.keys(obj)
objKeys.forEach((i, index) => {
const iArr = splitPosition(i)
let saved = false
checkedPositionArr.forEach((_pos) => {
const _posArr = splitPosition(_pos)
if (iArr.length > _posArr.length && isInclude(_posArr, iArr)) {
obj[i].halfChecked = false
obj[i].checked = checkIt
objKeys[index] = null
}
if (iArr[0] === _posArr[0] && iArr[1] === _posArr[1]) {
saved = true
}
})
if (!saved) {
objKeys[index] = null
}
})
objKeys = objKeys.filter(i => i) // filter non null;
for (let pIndex = 0; pIndex < checkedPositionArr.length; pIndex++) {
// loop to set ancestral nodes's `checked` or `halfChecked`
const loop = (__pos) => {
const _posLen = splitPosition(__pos).length
if (_posLen <= 2) { // e.g. '0-0', '0-1'
return
}
let sibling = 0
let siblingChecked = 0
const parentPosition = stripTail(__pos)
objKeys.forEach((i /* , index*/) => {
const iArr = splitPosition(i)
if (iArr.length === _posLen && isInclude(splitPosition(parentPosition), iArr)) {
sibling++
if (obj[i].checked) {
siblingChecked++
const _i = checkedPositionArr.indexOf(i)
if (_i > -1) {
checkedPositionArr.splice(_i, 1)
if (_i <= pIndex) {
pIndex--
}
}
} else if (obj[i].halfChecked) {
siblingChecked += 0.5
}
// objKeys[index] = null;
}
})
// objKeys = objKeys.filter(i => i); // filter non null;
const parent = obj[parentPosition]
// not check, checked, halfChecked
if (siblingChecked === 0) {
parent.checked = false
parent.halfChecked = false
} else if (siblingChecked === sibling) {
parent.checked = true
parent.halfChecked = false
} else {
parent.halfChecked = true
parent.checked = false
}
loop(parentPosition)
}
loop(checkedPositionArr[pIndex], pIndex)
}
// console.log(Date.now()-s, objKeys.length, checkIt);
}
function getCheck (treeNodesStates, checkedPositions) {
const halfCheckedKeys = []
const checkedKeys = []
const checkedNodes = []
Object.keys(treeNodesStates).forEach((item) => {
const itemObj = treeNodesStates[item]
if (itemObj.checked) {
checkedKeys.push(itemObj.key)
// checkedNodes.push(getValuePropValue(itemObj.node));
checkedNodes.push({ ...itemObj, pos: item })
} else if (itemObj.halfChecked) {
halfCheckedKeys.push(itemObj.key)
}
})
return {
halfCheckedKeys, checkedKeys, checkedNodes, treeNodesStates, checkedPositions,
}
}
export function getTreeNodesStates (children, values) {
const checkedPositions = []
const treeNodesStates = {}
loopAllChildren(children, (item, index, pos, keyOrPos, siblingPosition) => {
treeNodesStates[pos] = {
node: item,
key: keyOrPos,
checked: false,
halfChecked: false,
siblingPosition,
}
if (values.indexOf(getValuePropValue(item)) !== -1) {
treeNodesStates[pos].checked = true
checkedPositions.push(pos)
}
})
handleCheckState(treeNodesStates, filterParentPosition(checkedPositions.sort()), true)
return getCheck(treeNodesStates, checkedPositions)
}
// can add extra prop to every node.
export function recursiveCloneChildren (children, cb = ch => ch) {
// return React.Children.map(children, child => {
return Array.from(children).map(child => {
const newChild = cb(child)
if (newChild && newChild.props && newChild.props.children) {
return cloneElement(newChild, {
children: recursiveCloneChildren(newChild.props.children, cb),
})
}
return newChild
})
}
// const newChildren = recursiveCloneChildren(children, child => {
// const extraProps = {};
// if (child && child.type && child.type.xxx) {
// extraProps._prop = true;
// return React.cloneElement(child, extraProps);
// }
// return child;
// });
function recursiveGen (children, level = 0) {
return children.map((child, index) => {
const pos = `${level}-${index}`
const props = getAllProps(child)
const { title, label, value, ...rest } = props
const { children: subChildren } = child.componentOptions
const o = {
...rest,
title,
label: label || title,
value,
key: child.key,
_pos: pos,
}
if (subChildren) {
o.children = recursiveGen(subChildren, pos)
}
return o
})
}
function recursive (children, cb) {
children.forEach(item => {
cb(item)
if (item.children) {
recursive(item.children, cb)
}
})
}
// Get the tree's checkedNodes (todo: can merge to the `handleCheckState` function)
// If one node checked, it's all children nodes checked.
// If sibling nodes all checked, the parent checked.
export function filterAllCheckedData (vs, treeNodes) {
const vals = [...vs]
if (!vals.length) {
return vals
}
const data = recursiveGen(treeNodes)
const checkedNodesPositions = []
function checkChildren (children) {
children.forEach(item => {
if (item.__checked) {
return
}
const ci = vals.indexOf(item.value)
const childs = item.children
if (ci > -1) {
item.__checked = true
checkedNodesPositions.push({ node: item, pos: item._pos })
vals.splice(ci, 1)
if (childs) {
recursive(childs, child => {
child.__checked = true
checkedNodesPositions.push({ node: child, pos: child._pos })
})
}
} else {
if (childs) {
checkChildren(childs)
}
}
})
}
function checkParent (children, parent = { root: true }) {
let siblingChecked = 0
children.forEach(item => {
const childs = item.children
if (childs && !item.__checked && !item.__halfChecked) {
const p = checkParent(childs, item)
if (p.__checked) {
siblingChecked++
} else if (p.__halfChecked) {
siblingChecked += 0.5
}
} else if (item.__checked) {
siblingChecked++
} else if (item.__halfChecked) {
siblingChecked += 0.5
}
})
const len = children.length
if (siblingChecked === len) {
parent.__checked = true
checkedNodesPositions.push({ node: parent, pos: parent._pos })
} else if (siblingChecked < len && siblingChecked > 0) {
parent.__halfChecked = true
}
if (parent.root) {
return children
}
return parent
}
checkChildren(data)
checkParent(data)
checkedNodesPositions.forEach((i, index) => {
// clear private metadata
delete checkedNodesPositions[index].node.__checked
delete checkedNodesPositions[index].node._pos
// create the same structure of `onCheck`'s return.
checkedNodesPositions[index].node.props = {
title: checkedNodesPositions[index].node.title,
label: checkedNodesPositions[index].node.label || checkedNodesPositions[index].node.title,
value: checkedNodesPositions[index].node.value,
}
if (checkedNodesPositions[index].node.children) {
checkedNodesPositions[index].node.props.children = checkedNodesPositions[index].node.children
}
delete checkedNodesPositions[index].node.title
delete checkedNodesPositions[index].node.label
delete checkedNodesPositions[index].node.value
delete checkedNodesPositions[index].node.children
})
return checkedNodesPositions
}
export function processSimpleTreeData (treeData, format) {
function unflatten2 (array, parent = { [format.id]: format.rootPId }) {
const children = []
for (let i = 0; i < array.length; i++) {
array[i] = { ...array[i] } // copy, can not corrupts original data
if (array[i][format.pId] === parent[format.id]) {
array[i].key = array[i][format.id]
children.push(array[i])
array.splice(i--, 1)
}
}
if (children.length) {
parent.children = children
children.forEach(child => unflatten2(array, child))
}
if (parent[format.id] === format.rootPId) {
return children
}
}
return unflatten2(treeData)
}
export function saveRef (instance, name) {
if (!instance.saveRefs) {
instance.saveRefs = {}
}
if (!instance.saveRefs[name]) {
instance.saveRefs[name] = (node) => {
instance[name] = node
}
}
return instance.saveRefs[name]
}

View File

@ -558,7 +558,7 @@ const Tree = {
tabIndex={focusable ? '0' : null}
onKeydown={focusable ? this.onKeydown : () => {}}
>
{children.map(this.renderTreeNode)}
{children.map((child, index) => this.renderTreeNode(child, index))}
</ul>
)
},

View File

@ -21,6 +21,7 @@ let onlyTreeNodeWarned = false // Only accept TreeNode
const TreeNode = {
name: 'TreeNode',
mixins: [BaseMixin],
__ANT_TREE_NODE: true,
props: initDefaultProps({
eventKey: PropTypes.string, // Pass by parent `cloneElement`
prefixCls: PropTypes.string,

View File

@ -332,6 +332,11 @@ export function calcCheckStateConduct (treeNodes, checkedKeys) {
}
}
// function keyListToString (keyList) {
// if (!keyList) return keyList
// return keyList.map(key => String(key))
// }
/**
* Calculate the value of checked and halfChecked keys.
* This should be only run in init or props changed.
@ -361,6 +366,9 @@ export function calcCheckedKeys (keys, props, children = []) {
return null
}
// keyProps.checkedKeys = keyListToString(keyProps.checkedKeys)
// keyProps.halfCheckedKeys = keyListToString(keyProps.halfCheckedKeys)
// Do nothing if is checkStrictly mode
if (checkStrictly) {
return keyProps

View File

@ -45,7 +45,7 @@ import {
Table,
Transfer,
Tree,
// TreeSelect,
TreeSelect,
Tabs,
Tag,
TimePicker,
@ -133,7 +133,8 @@ Vue.component(Table.ColumnGroup.name, Table.ColumnGroup)
Vue.component(Transfer.name, Transfer)
Vue.component(Tree.name, Tree)
Vue.component(Tree.TreeNode.name, Tree.TreeNode)
// Vue.component(TreeSelect.name, TreeSelect)
Vue.component(TreeSelect.name, TreeSelect)
Vue.component(TreeSelect.TreeNode.name, TreeSelect.TreeNode)
Vue.component(Tabs.name, Tabs)
Vue.component(Tabs.TabPane.name, Tabs.TabPane)
Vue.component(Tag.name, Tag)

View File

@ -43,6 +43,7 @@ export { default as inputNumber } from 'antd/input-number/demo/index.vue'
export { default as transfer } from 'antd/transfer/demo/index.vue'
export { default as upload } from 'antd/upload/demo/index.vue'
export { default as tree } from 'antd/tree/demo/index.vue'
export { default as treeSelect } from 'antd/tree-select/demo/index.vue'
export { default as layout } from 'antd/layout/demo/index.vue'
export { default as form } from 'antd/form/demo/index.vue'
export { default as anchor } from 'antd/anchor/demo/index.vue'

View File

@ -48,6 +48,7 @@ Array [
"Table",
"Transfer",
"Tree",
"TreeSelect",
"Tabs",
"Tag",
"TimePicker",