Refactor tree select (#335)

* refactor: tree-select
This commit is contained in:
tangjinzhou 2018-12-25 12:00:39 +08:00 committed by GitHub
parent ff7a426d55
commit e23265c74c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 3018 additions and 2051 deletions

View File

@ -1,4 +1,3 @@
import { getOptionProps } from './props-util'
export default {
directives: {
@ -17,9 +16,9 @@ export default {
methods: {
setState (state, callback) {
const newState = typeof state === 'function' ? state(this.$data) : state
if (this.getDerivedStateFromProps) {
Object.assign(newState, this.getDerivedStateFromProps(getOptionProps(this), this.$data, true) || {})
}
// if (this.getDerivedStateFromProps) {
// Object.assign(newState, this.getDerivedStateFromProps(getOptionProps(this), { ...this.$data, ...newState }, true) || {})
// }
Object.assign(this.$data, newState)
this.$nextTick(() => {
callback && callback()

View File

@ -1,25 +1,19 @@
// 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" 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;"><i class="ant-select-arrow-icon anticon anticon-down"><svg viewBox="64 64 896 896" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></i></span></span></span>
`;
exports[`renders ./components/tree-select/demo/basic.md correctly 1`] = `<span role="combobox" aria-haspopup="listbox" tabindex="0" class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span 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;"><i class="ant-select-arrow-icon anticon anticon-down"><svg viewBox="64 64 896 896" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></i></span></span></span>`;
exports[`renders ./components/tree-select/demo/checkable.md correctly 1`] = `
<span class="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"><i class="ant-select-remove-icon anticon anticon-close"><svg viewBox="64 64 896 896" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></i></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>
<span role="combobox" aria-haspopup="listbox" tabindex="-1" class="ant-select ant-select-enabled" style="width: 300px;"><span class="ant-select-selection ant-select-selection--multiple"><div class="ant-select-selection__rendered"><li unselectable="unselectable" role="menuitem" title="Node1" class="ant-select-selection__choice"><span class="ant-select-selection__choice__remove"><i class="ant-select-remove-icon anticon anticon-close"><svg viewBox="64 64 896 896" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></i></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 type="text" aria-label="filter select" aria-autocomplete="list" aria-multiline="false" class="ant-select-search__field" style="width: 0px;"><span class="ant-select-search__field__mirror">&nbsp;</span></span></li>
</div>
</span></span>
`;
exports[`renders ./components/tree-select/demo/multiple.md correctly 1`] = `
<span class="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>
<span role="combobox" aria-haspopup="listbox" tabindex="-1" class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span 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 type="text" aria-label="filter select" aria-autocomplete="list" aria-multiline="false" class="ant-select-search__field" style="width: 0px;"><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" 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;"><i class="ant-select-arrow-icon anticon anticon-down"><svg viewBox="64 64 896 896" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></i></span></span></span>
`;
exports[`renders ./components/tree-select/demo/suffix.md correctly 1`] = `<span role="combobox" aria-haspopup="listbox" tabindex="0" class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span 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;"><i class="anticon anticon-smile"><svg viewBox="64 64 896 896" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm352 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 0 1 248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 0 1 249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 0 1 775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 0 1 775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 0 0-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 0 0-8-8.4z"></path></svg></i></span></span></span>`;
exports[`renders ./components/tree-select/demo/treeData.md correctly 1`] = `<span role="combobox" aria-haspopup="listbox" tabindex="0" class="ant-select ant-select-enabled" style="width: 300px;"><span 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;"><i class="ant-select-arrow-icon anticon anticon-down"><svg viewBox="64 64 896 896" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></i></span></span></span>`;

View File

@ -22,7 +22,7 @@ The most basic usage.
>
<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 :selectable="false" 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'>
@ -39,12 +39,13 @@ The most basic usage.
export default {
data () {
return {
treeExpandedKeys: [],
value: undefined,
}
},
methods: {
onChange (value) {
console.log(arguments)
console.log(value)
this.value = value
},
},

View File

@ -18,7 +18,6 @@ Multiple and checkable.
treeCheckable
:showCheckedStrategy="SHOW_PARENT"
searchPlaceholder='Please select'
treeNodeFilterProp='label'
/>
</template>
@ -27,29 +26,29 @@ import { TreeSelect } from 'ant-design-vue'
const SHOW_PARENT = TreeSelect.SHOW_PARENT
const treeData = [{
label: 'Node1',
title: 'Node1',
value: '0-0',
key: '0-0',
children: [{
label: 'Child Node1',
title: 'Child Node1',
value: '0-0-0',
key: '0-0-0',
}],
}, {
label: 'Node2',
title: 'Node2',
value: '0-1',
key: '0-1',
children: [{
label: 'Child Node3',
title: 'Child Node3',
value: '0-1-0',
key: '0-1-0',
disabled: true,
}, {
label: 'Child Node4',
title: 'Child Node4',
value: '0-1-1',
key: '0-1-1',
}, {
label: 'Child Node5',
title: 'Child Node5',
value: '0-1-2',
key: '0-1-2',
}],
@ -64,7 +63,7 @@ export default {
},
methods: {
onChange (value) {
console.log('onChange ', value, arguments)
console.log('onChange ', value)
this.value = value
},
},

View File

@ -3,6 +3,7 @@ import Basic from './basic'
import Checkable from './checkable'
import Multiple from './multiple'
import TreeData from './treeData'
import Suffix from './suffix'
import CN from '../index.zh-CN.md'
import US from '../index.en-US.md'
@ -35,6 +36,7 @@ export default {
<Checkable/>
<Multiple/>
<TreeData/>
<Suffix />
<api>
<template slot='cn'>
<CN/>

View File

@ -47,14 +47,14 @@ export default {
},
methods: {
onChange (value) {
console.log(arguments)
console.log(value)
this.value = value
},
onSearch () {
console.log(arguments)
console.log(...arguments)
},
onSelect () {
console.log(arguments)
console.log(...arguments)
},
},
}

View File

@ -0,0 +1,54 @@
<cn>
#### 后缀图标
最简单的用法。
</cn>
<us>
#### Suffix
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-icon slot="suffixIcon" type="smile" />
<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(value)
this.value = value
},
},
}
</script>
```

View File

@ -16,10 +16,9 @@ The tree structure can be populated using `treeData` property. This is a quick a
: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'">
<span style="color: #08c" slot="title" slot-scope="{key, value}" v-if="key='0-0-1'">
<a-icon type="home"/>Child Node1 {{value}}
</span>
</a-tree-select>
@ -27,22 +26,22 @@ The tree structure can be populated using `treeData` property. This is a quick a
<script>
const treeData = [{
label: 'Node1',
title: 'Node1',
value: '0-0',
key: '0-0',
children: [{
value: '0-0-1',
key: '0-0-1',
scopedSlots: { // custom label
label: 'label',
scopedSlots: { // custom title
title: 'title',
},
}, {
label: 'Child Node2',
title: 'Child Node2',
value: '0-0-2',
key: '0-0-2',
}],
}, {
label: 'Node2',
title: 'Node2',
value: '0-1',
key: '0-1',
}]

View File

@ -21,16 +21,17 @@
| 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' |
| suffixIcon | The custom suffix icon | VNode \| slot | - |
| 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\[] | - |
| treeExpandedKeys | Set expanded keys | 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\[] | - |
| suffixIcon | The custom suffix icon | VNode \| slot | - |
### Events
| Events Name | Description | Arguments |
@ -38,6 +39,7 @@
| 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) |
| treeExpand | A callback function, can be executed when treeNode expanded | function(expandedKeys) |
### Tree Methods
@ -52,6 +54,7 @@
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| selectable | can be selected | boolean | true |
| disableCheckbox | Disables the checkbox of the treeNode | boolean | false |
| disabled | Disabled or not | boolean | false |
| isLeaf | Leaf node or not | boolean | false |

View File

@ -136,10 +136,12 @@ const TreeSelect = {
dropdownStyle: { maxHeight: '100vh', overflow: 'auto', ...dropdownStyle },
treeCheckable: checkable,
notFoundContent: notFoundContent || locale.notFoundContent,
__propsSymbol__: Symbol(),
},
class: cls,
on: { ...this.$listeners, change: this.onChange },
ref: 'vcTreeSelect',
scopedSlots: this.$scopedSlots,
}
return (
<VcTreeSelect {...VcTreeSelectProps}>{filterEmpty(this.$slots.default)}</VcTreeSelect>

View File

@ -21,24 +21,26 @@
| 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' |
| suffixIcon | 自定义的选择框后缀图标 | VNode \| slot | - |
| 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\[] | - |
| treeExpandedKeys | 设置展开的树节点 | string\[] | - |
| treeNodeFilterProp | 输入项过滤对应的 treeNode 属性 | string | 'value' |
| treeNodeLabelProp | 作为显示的 prop 设置 | string | 'title' |
| value(v-model) | 指定当前选中的条目 | string/string\[] | - |
| suffixIcon | 自定义的选择框后缀图标 | VNode \| slot | - |
### 事件
| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| change | 选中树节点时调用此函数 | function(value, label, extra) | - |
| search | 文本框值变化时回调 | function(value: string) | - |
| select | 被选中时调用 | function(value, node, extra) | - |
| change | 选中树节点时调用此函数 | function(value, label, extra) |
| search | 文本框值变化时回调 | function(value: string) |
| select | 被选中时调用 | function(value, node, extra) |
| treeExpand | 展开节点时调用 | function(expandedKeys) |
### Tree 方法
@ -53,6 +55,7 @@
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| selectable | 是否可选 | boolean | true |
| disableCheckbox | 禁掉 checkbox | boolean | false |
| disabled | 是否禁用 | boolean | false |
| isLeaf | 是否是叶子节点 | boolean | false |

View File

@ -11,6 +11,14 @@ export const TreeData = PropTypes.shape({
export const TreeSelectProps = () => ({
...AbstractSelectProps(),
autoFocus: PropTypes.bool,
dropdownStyle: PropTypes.object,
filterTreeNode: PropTypes.oneOfType([Function, Boolean]),
getPopupContainer: PropTypes.func,
labelInValue: PropTypes.bool,
loadData: PropTypes.func,
maxTagCount: PropTypes.number,
maxTagPlaceholder: PropTypes.any,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.array]),
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
multiple: PropTypes.bool,
@ -18,21 +26,18 @@ export const TreeSelectProps = () => ({
// 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,
showCheckedStrategy: PropTypes.oneOf(['SHOW_ALL', 'SHOW_PARENT', 'SHOW_CHILD']),
suffixIcon: PropTypes.any,
treeCheckable: PropTypes.bool,
treeDefaultExpandedKeys: PropTypes.arrayOf(String),
filterTreeNode: PropTypes.func,
treeNodeFilterProp: PropTypes.string,
treeNodeLabelProp: PropTypes.string,
treeCheckStrictly: PropTypes.bool,
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,
suffixIcon: PropTypes.any,
dropdownClassName: PropTypes.string,
dropdownMatchSelectWidth: PropTypes.bool,
treeDefaultExpandAll: PropTypes.bool,
treeExpandedKeys: PropTypes.arrayOf(String),
treeDefaultExpandedKeys: PropTypes.arrayOf(String),
treeNodeFilterProp: PropTypes.string,
treeNodeLabelProp: PropTypes.string,
})

View File

@ -2,7 +2,7 @@ import warning from 'warning'
import { Tree as VcTree, TreeNode } from '../vc-tree'
import animation from '../_util/openAnimation'
import PropTypes from '../_util/vue-types'
import { initDefaultProps, getOptionProps } from '../_util/props-util'
import { initDefaultProps, getOptionProps, filterEmpty } from '../_util/props-util'
import Icon from '../icon'
function TreeProps () {
@ -179,7 +179,7 @@ export default {
props: {
...props,
checkable: checkable ? <span class={`${prefixCls}-checkbox-inner`} /> : checkable,
children: this.$slots.default || [],
children: filterEmpty(this.$slots.default || []),
__propsSymbol__: Symbol(),
switcherIcon: this.renderSwitcherIcon,
},

View File

@ -107,7 +107,7 @@ const Select = {
this.__propsSymbol__,
'Replace slots.default with props.children and pass props.__propsSymbol__'
)
return {
const state = {
_value: this.getValueFromProps(props, true), // true: use default value
_inputValue: props.combobox ? this.getInputValueForCombobox(
props,
@ -119,10 +119,10 @@ const Select = {
// a flag for aviod redundant getOptionsInfoFromProps call
_skipBuildOptionsInfo: true,
}
},
beforeMount () {
const state = this.getDerivedStateFromProps(getOptionProps(this), this.$data)
Object.assign(this.$data, state)
return {
...state,
...this.getDerivedStateFromProps(props, state),
}
},
mounted () {

View File

@ -37,7 +37,8 @@
top: 1px;
right: 1px;
width: 20px;
b {
&:after {
content: '';
border-color: #999999 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
@ -66,22 +67,30 @@
&__clear {
font-weight: bold;
position: absolute;
}
}
&:after {
content: '×'
&-enabled {
.@{selectPrefixCls}-selection {
&:hover {
border-color: #23c0fa;
box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
}
&:active {
border-color: #2db7f5;
}
}
&.@{selectPrefixCls}-focused {
.@{selectPrefixCls}-selection {
//border-color: #23c0fa;
border-color: #7700fa;
box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
}
}
}
&-enabled &-selection {
&:hover {
border-color: #23c0fa;
box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
}
&:active {
border-color: #2db7f5;
}
}
&-selection--single {
height: 28px;
@ -105,6 +114,9 @@
.@{selectPrefixCls}-selection__clear {
top: 5px;
right: 20px;
&:after {
content: '×';
}
}
}
@ -117,7 +129,7 @@
cursor: not-allowed;
color: #ccc;
&:hover {
&:hover{
cursor: not-allowed;
color: #ccc;
}
@ -130,6 +142,7 @@
}
&-search__field__placeholder {
display: block;
position: absolute;
top: 0;
left: 3px;
@ -288,6 +301,7 @@
top: 0;
right: 2px;
transition: opacity .3s, transform .3s;
&:before {
content: '×'
}
@ -487,7 +501,7 @@
}
&-open {
.@{selectPrefixCls}-arrow b {
.@{selectPrefixCls}-arrow:after {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px;
}
@ -498,3 +512,25 @@
padding: 8px;
}
}
.custom-icon-demo {
.@{selectPrefixCls} {
&-selection__choice__remove {
&:before {
content: '';
}
}
&-arrow {
&:after {
display: none;
}
}
&-selection__clear {
&:after {
content: '';
}
}
}
}

View File

@ -38,7 +38,7 @@
}
}
&.filter-node {
> a {
> .@{treePrefixCls}-node-content-wrapper {
color: #a60000!important;
font-weight: bold!important;
}

View File

@ -1,12 +1,11 @@
/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
import BaseMixin from '../../_util/BaseMixin'
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 TreeSelect, { TreeNode, SHOW_PARENT } from '../src/index'
import { gData } from './util'
import './demo.less'
function isLeaf (value) {
if (!value) {
@ -51,89 +50,90 @@ function findPath (value, data) {
}
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);
},
mixins: [BaseMixin],
data: () => ({
tsOpen: false,
visible: false,
searchValue: '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: [],
simpleSearchValue: 'test111',
simpleTreeData: [
{ key: 1, pId: 0, label: 'test1', value: 'test1' },
{ key: 121, pId: 0, label: 'test2', value: 'test2' },
{ 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,
},
}),
methods: {
onClick () {
this.visible = true
onClick () {
this.setState({
visible: true,
})
},
onClose () {
this.visible = false
onClose () {
this.setState({
visible: false,
})
},
onSearch (value) {
console.log(value, arguments)
console.log('Do Search:', value, arguments)
this.setState({ searchValue: value })
},
onChange (value) {
console.log('onChange', arguments)
this.value = value
onChange (value, ...rest) {
console.log('onChange', value, ...rest)
this.setState({ value })
},
onChangeChildren (value) {
console.log('onChangeChildren', arguments)
onChangeChildren (...args) {
console.log('onChangeChildren', ...args)
const value = args[0]
const pre = value ? this.value : undefined
this.value = isLeaf(value) ? value : pre
this.setState({ value: isLeaf(value) ? value : pre })
},
onChangeLV (value) {
onChangeLV (value) {
console.log('labelInValue', arguments)
if (!value) {
this.lv = undefined
this.setState({ lv: undefined })
return
}
const path = findPath(value.value, gData).map(i => i.label).reverse().join(' > ')
this.lv = { value: value.value, label: path }
this.setState({ lv: { value: value.value, label: path }})
},
onMultipleChange (value) {
console.log('onMultipleChange', arguments)
this.multipleValue = value
this.setState({ multipleValue: value })
},
onSelect () {
// use onChange instead
console.log(...arguments)
console.log(arguments)
},
onDropdownVisibleChange (visible, info) {
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.')
window.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
filterTreeNode (input, child) {
return String(child.data.props.title).indexOf(input) === 0
},
},
@ -167,10 +167,10 @@ export default {
onSearch={this.onSearch}
onChange={this.onChange}
onSelect={this.onSelect}
__propsSymbol__={Symbol()}
/>
</div>
</Dialog> : null}
<h2>single select</h2>
<TreeSelect
style={{ width: '300px' }}
@ -180,31 +180,35 @@ export default {
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
showSearch allowClear treeLine
inputValue={this.inputValue}
searchValue={this.searchValue}
value={this.value}
treeData={gData}
treeNodeFilterProp='label'
filterTreeNode={false}
onSearch={this.onSearch}
open={this.tsOpen}
onChange={(value) => {
console.log('onChange', value, arguments)
onChange={(value, ...args) => {
console.log('onChange', value, ...args)
if (value === '0-0-0-0-value') {
this.tsOpen = true
this.setState({ tsOpen: true })
} else {
this.tsOpen = false
this.setState({ tsOpen: false })
}
this.value = value
this.setState({ value })
} }
dropdownVisibleChange={(v, info) => {
console.log('single dropdownVisibleChange', v, info)
console.log('single onDropdownVisibleChange', v, info)
// document clicked
if (info.documentClickClose && this.value === '0-0-0-0-value') {
return false
}
this.setState({
tsOpen: v,
})
return true
} }
onSelect={this.onSelect}
__propsSymbol__={Symbol()}
/>
<h2>single select (just select children)</h2>
@ -221,10 +225,11 @@ export default {
treeNodeFilterProp='label'
filterTreeNode={false}
onChange={this.onChangeChildren}
__propsSymbol__={Symbol()}
/>
<h2>multiple select</h2>
<TreeSelect ref='mul'
<TreeSelect
style={{ width: '300px' }}
transitionName='rc-tree-select-dropdown-slide-up'
choiceTransitionName='rc-tree-select-selection__choice-zoom'
@ -238,6 +243,7 @@ export default {
onChange={this.onMultipleChange}
onSelect={this.onSelect}
allowClear
__propsSymbol__={Symbol()}
/>
<h2>check select</h2>
@ -247,17 +253,23 @@ export default {
choiceTransitionName='rc-tree-select-selection__choice-zoom'
dropdownStyle={{ height: '200px', overflow: 'auto' }}
dropdownPopupAlign={{ overflow: { adjustY: 0, adjustX: 0 }, offset: [0, 2] }}
onDropdownVisibleChange={this.onDropdownVisibleChange}
dropdownVisibleChange={this.onDropdownVisibleChange}
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
treeLine maxTagTextLength={10}
value={this.value}
inputValue={null}
autoClearSearchValue
treeData={gData}
treeNodeFilterProp='title'
treeCheckable showCheckedStrategy={SHOW_PARENT}
onChange={this.onChange}
onSelect={this.onSelect}
maxTagCount={2}
maxTagPlaceholder={(valueList) => {
console.log('Max Tag Rest Value:', valueList)
return `${valueList.length} rest...`
}}
__propsSymbol__={Symbol()}
/>
<h2>labelInValue & show path</h2>
@ -274,6 +286,7 @@ export default {
treeNodeFilterProp='label'
filterTreeNode={false}
onChange={this.onChangeLV}
__propsSymbol__={Symbol()}
/>
<h2>use treeDataSimpleMode</h2>
@ -283,7 +296,10 @@ export default {
placeholder={<i>请下拉选择</i>}
searchPlaceholder='please search'
treeLine maxTagTextLength={10}
inputValue={'test111'}
searchValue={this.simpleSearchValue}
onSearch={(simpleSearchValue) => {
this.setState({ simpleSearchValue })
}}
value={this.value}
treeData={this.simpleTreeData}
treeNodeFilterProp='title'
@ -291,13 +307,14 @@ export default {
treeCheckable showCheckedStrategy={SHOW_PARENT}
onChange={this.onChange}
onSelect={this.onSelect}
__propsSymbol__={Symbol()}
/>
<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}
defaultValue='leaf1' multiple treeCheckable showCheckedStrategy={SHOW_PARENT}
treeDefaultExpandAll
treeData={[
{ key: '', value: '', label: 'empty value', children: [] },
@ -308,18 +325,20 @@ export default {
],
},
]}
onChange={(val) => console.log(val, arguments)}
onChange={(val, ...args) => console.log(val, ...args)}
__propsSymbol__={Symbol()}
/>
<h2>use TreeNode Component (not recommend)</h2>
<TreeSelect
style={{ width: '200px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
defaultValue={'leaf1'}
defaultValue='leaf1'
treeDefaultExpandAll
treeNodeFilterProp='title'
filterTreeNode={this.filterTreeNode}
onChange={(val) => console.log(val, arguments)}
onChange={(val, ...args) => console.log(val, ...args)}
__propsSymbol__={Symbol()}
>
<TreeNode value='' title='parent 1' key=''>
<TreeNode value='parent 1-0' title='parent 1-0' key='0-1-0'>
@ -331,7 +350,7 @@ export default {
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 value='same value10' title='same titlexd' key='0-1-1-1-0' style={{ color: 'red', background: 'green' }} />
</TreeNode>
</TreeNode>
</TreeNode>

View File

@ -56,6 +56,7 @@ export default {
treeCheckable
showCheckedStrategy={SHOW_PARENT}
onChange={this.onChange}
__propsSymbol__={Symbol()}
/>
</div>
<div>
@ -70,6 +71,7 @@ export default {
treeCheckStrictly
showCheckedStrategy={SHOW_PARENT}
onChange={this.onChangeStrictly}
__propsSymbol__={Symbol()}
/>
</div>
</div>

View File

@ -0,0 +1,68 @@
/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
import BaseMixin from '../../_util/BaseMixin'
import '../assets/index.less'
import '../../vc-dialog/assets/index.less'
import TreeSelect, { TreeNode } from '../src/index'
import './demo.less'
export default {
mixins: [BaseMixin],
data: () => ({
treeExpandedKeys: [],
}),
methods: {
onTreeExpand (treeExpandedKeys) {
this.setState({
treeExpandedKeys,
})
},
setTreeExpandedKeys () {
this.setState({
treeExpandedKeys: ['000', '0-1-0'],
})
},
},
render () {
const { treeExpandedKeys } = this
return (
<div>
<h2>Conrolled treeExpandedKeys</h2>
<TreeSelect
style={{ width: '200px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
treeExpandedKeys={treeExpandedKeys}
onTreeExpand={this.onTreeExpand}
__propsSymbol__={Symbol()}
>
<TreeNode value='' title='parent 1' key='000'>
<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' style={{ color: 'red', background: 'green' }} />
</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>
<button onClick={this.setTreeExpandedKeys}>
Set treeExpandedKeys
</button>
</div>
)
},
}

View File

@ -0,0 +1,113 @@
/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
import '../assets/index.less'
import '../../vc-dialog/assets/index.less'
import TreeSelect from '../src/index'
import { gData } from './util'
import './demo.less'
const bubblePath = 'M632 888H392c-4.4 0-8 3.6-8 8v32c0 ' +
'17.7 14.3 32 32 32h192c17.7 0 32-14.3 32-32v-3' +
'2c0-4.4-3.6-8-8-8zM512 64c-181.1 0-328 146.9-3' +
'28 328 0 121.4 66 227.4 164 284.1V792c0 17.7 1' +
'4.3 32 32 32h264c17.7 0 32-14.3 32-32V676.1c98' +
'-56.7 164-162.7 164-284.1 0-181.1-146.9-328-32' +
'8-328z m127.9 549.8L604 634.6V752H420V634.6l-3' +
'5.9-20.8C305.4 568.3 256 484.5 256 392c0-141.4' +
' 114.6-256 256-256s256 114.6 256 256c0 92.5-49' +
'.4 176.3-128.1 221.8z'
const clearPath = 'M793 242H366v-74c0-6.7-7.7-10.4-12.9' +
'-6.3l-142 112c-4.1 3.2-4.1 9.4 0 12.6l142 112c' +
'5.2 4.1 12.9 0.4 12.9-6.3v-74h415v470H175c-4.4' +
' 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h618c35.3 0 64-' +
'28.7 64-64V306c0-35.3-28.7-64-64-64z'
const arrowPath = 'M765.7 486.8L314.9 134.7c-5.3-4.1' +
'-12.9-0.4-12.9 6.3v77.3c0 4.9 2.3 9.6 6.1 12.6l36' +
'0 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6' +
'.7 7.7 10.4 12.9 6.3l450.8-352.1c16.4-12.8 16.4-3' +
'7.6 0-50.4z'
const getSvg = (h, path, iStyle = {}, style = {}) => {
return (
<i style={iStyle}>
<svg
viewBox='0 0 1024 1024'
width='1em'
height='1em'
fill='currentColor'
style={{ verticalAlign: '-.125em', ...style }}
>
<path d={path} />
</svg>
</i>
)
}
export default {
data () {
const h = this.$createElement
const switcherIcon = (obj) => {
if (obj.isLeaf) {
return getSvg(h, arrowPath,
{ cursor: 'pointer', backgroundColor: 'white' },
{ transform: 'rotate(270deg)' })
}
return getSvg(h, arrowPath,
{ cursor: 'pointer', backgroundColor: 'white' },
{ transform: `rotate(${obj.expanded ? 90 : 0}deg)` })
}
const inputIcon = getSvg(h, bubblePath)
const clearIcon = getSvg(h, clearPath)
const removeIcon = getSvg(h, clearPath)
return {
iconProps: {
inputIcon,
clearIcon,
removeIcon,
switcherIcon,
},
iconPropsFunction: {
inputIcon: () => inputIcon,
clearIcon: () => clearIcon,
removeIcon: () => removeIcon,
switcherIcon,
},
}
},
render () {
return (
<div class='custom-icon-demo'>
<h2>Single</h2>
<TreeSelect
treeData={gData}
placeholder={<span>Please Select</span>}
transitionName='rc-tree-select-dropdown-slide-up'
style={{ width: '300px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto', zIndex: 1500 }}
showSearch allowClear
{...{ props: { ...this.iconProps }}}
__propsSymbol__={Symbol()}
/>
<br />
<h2>Multiple</h2>
<TreeSelect
treeData={gData}
multiple
placeholder={<span>Please Select</span>}
transitionName='rc-tree-select-dropdown-slide-up'
style={{ width: '300px' }}
dropdownStyle={{ maxHeight: '200px', overflow: 'auto', zIndex: 1500 }}
showSearch allowClear
{...{ props: { ...this.iconPropsFunction }}}
__propsSymbol__={Symbol()}
/>
</div>
)
},
}

View File

@ -1,4 +1,3 @@
.rc-tree-select-selection--multiple {
max-height: 50px;
overflow-y: scroll;
@ -12,4 +11,4 @@
.rc-tree-select-selection--multiple {
min-height: 50px;
}
}
}

View File

@ -1,6 +1,7 @@
/* eslint react/no-multi-comp:0, no-console:0 */
import BaseMixin from '../../_util/BaseMixin'
import '../assets/index.less'
import TreeSelect from '../index'
import TreeSelect from '../src/index'
const SHOW_PARENT = TreeSelect.SHOW_PARENT
@ -33,20 +34,18 @@ const treeData = [{
}]
export default {
data () {
return {
value: ['0-0-0'],
disabled: false,
}
},
mixins: [BaseMixin],
data: () => ({
value: ['0-0-0'],
disabled: false,
}),
methods: {
onChange (value) {
onChange (value) {
console.log('onChange ', value, arguments)
this.value = value
this.setState({ value })
},
switch (checked) {
this.disabled = checked
this.setState({ disabled: checked })
},
},
@ -61,6 +60,7 @@ export default {
treeCheckable: true,
showCheckedStrategy: SHOW_PARENT,
searchPlaceholder: 'Please select',
__propsSymbol__: Symbol(),
},
on: {
change: this.onChange,
@ -77,4 +77,3 @@ export default {
)
},
}

View File

@ -1,35 +1,36 @@
/* eslint react/no-multi-comp:0, no-console:0 */
import BaseMixin from '../../_util/BaseMixin'
import '../assets/index.less'
import TreeSelect from '../index'
import TreeSelect from '../src/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' },
}
},
mixins: [BaseMixin],
data: () => ({
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) {
onChange (value) {
console.log(value)
this.value = value
this.setState({
value,
})
},
onLoadData (treeNode) {
onLoadData (treeNode) {
console.log(treeNode)
return new Promise((resolve) => {
setTimeout(() => {
const treeData = [...this.treeData]
getNewTreeData(treeData, treeNode.eventKey, generateTreeNodes(treeNode), 2)
this.treeData = treeData
this.setState({ treeData })
resolve()
}, 500)
})
@ -47,9 +48,9 @@ export default {
value={this.value}
onChange={this.onChange}
loadData={this.onLoadData}
__propsSymbol__={Symbol()}
/>
</div>
)
},
}

View File

@ -71,6 +71,7 @@ export default {
treeCheckable
onChange={this.onChange}
onSelect={this.onSelect}
__propsSymbol__={Symbol()}
/>
<h2>use treeDataSimpleMode</h2>
@ -89,6 +90,7 @@ export default {
treeCheckable showCheckedStrategy={SHOW_PARENT}
onChange={this.onChange}
onSelect={this.onSelect}
__propsSymbol__={Symbol()}
/>
<button onClick={this.onDataChange}>change data</button>
</div>

View File

@ -25,7 +25,7 @@ const TreeSelectInput = {
render () {
return (
<TreeSelect {...{ props: this.$props }} onChange={this.onChange.bind(this)} />
<TreeSelect {...{ props: this.$props }} onChange={this.onChange} />
)
},
}
@ -57,6 +57,7 @@ const Form = {
multiple: true,
treeData: gData,
treeCheckable: true,
__propsSymbol__: Symbol(),
// treeDefaultExpandAll: true,
},
}

View File

@ -27,13 +27,15 @@ export function generateData (x = 3, y = 2, z = 1, gData = []) {
tns[index].children = []
return _loop(__level, key, tns[index].children)
})
return null
}
_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
/* eslint no-param-reassign:0 */
const rec = (n) => n >= 0 ? x * (y ** (n--)) + rec(n) : 0
return rec(z + 1)
}
console.log('总节点数单个tree', calcTotal())

View File

@ -1,7 +1,10 @@
// rc-tree-select 1.12.13 tag
// export this package's api
// base 2.4.4
import Vue from 'vue'
import TreeSelect from './src'
import ref from 'vue-ref'
Vue.use(ref, { name: 'ant-ref' })
export default TreeSelect
export { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './src'

View File

@ -0,0 +1,284 @@
import warning from 'warning'
import PropTypes from '../../../_util/vue-types'
import { Tree } from '../../../vc-tree'
import BaseMixin from '../../../_util/BaseMixin'
// export const popupContextTypes = {
// onPopupKeyDown: PropTypes.func.isRequired,
// onTreeNodeSelect: PropTypes.func.isRequired,
// onTreeNodeCheck: PropTypes.func.isRequired,
// }
function getDerivedStateFromProps (nextProps, prevState) {
const { _prevProps: prevProps = {},
_loadedKeys: loadedKeys,
_expandedKeyList: expandedKeyList,
_cachedExpandedKeyList: cachedExpandedKeyList,
} = prevState || {}
const {
valueList, valueEntities, keyEntities,
treeExpandedKeys, filteredTreeNodes, searchValue,
} = nextProps
const newState = {
_prevProps: { ...nextProps },
}
// Check value update
if (valueList !== prevProps.valueList) {
newState._keyList = valueList
.map(({ value }) => valueEntities[value])
.filter(entity => entity)
.map(({ key }) => key)
}
// Show all when tree is in filter mode
if (
!treeExpandedKeys &&
filteredTreeNodes &&
filteredTreeNodes.length &&
filteredTreeNodes !== prevProps.filteredTreeNodes
) {
newState._expandedKeyList = Object.keys(keyEntities)
}
// Cache `expandedKeyList` when filter set
if (searchValue && !prevProps.searchValue) {
newState._cachedExpandedKeyList = expandedKeyList
} else if (!searchValue && prevProps.searchValue && !treeExpandedKeys) {
newState._expandedKeyList = cachedExpandedKeyList || []
newState._cachedExpandedKeyList = []
}
// Use expandedKeys if provided
if (prevProps.treeExpandedKeys !== treeExpandedKeys) {
newState._expandedKeyList = treeExpandedKeys
}
// Clean loadedKeys if key not exist in keyEntities anymore
if (nextProps.loadData) {
newState._loadedKeys = loadedKeys.filter(key => key in keyEntities)
}
return newState
}
const BasePopup = {
mixins: [BaseMixin],
name: 'BasePopup',
props: {
prefixCls: PropTypes.string,
upperSearchValue: PropTypes.string,
valueList: PropTypes.array,
searchHalfCheckedKeys: PropTypes.array,
valueEntities: PropTypes.object,
keyEntities: PropTypes.object,
treeIcon: PropTypes.bool,
treeLine: PropTypes.bool,
treeNodeFilterProp: PropTypes.string,
treeCheckable: PropTypes.any,
treeCheckStrictly: PropTypes.bool,
treeDefaultExpandAll: PropTypes.bool,
treeDefaultExpandedKeys: PropTypes.array,
treeExpandedKeys: PropTypes.array,
loadData: PropTypes.func,
multiple: PropTypes.bool,
// onTreeExpand: PropTypes.func,
searchValue: PropTypes.string,
treeNodes: PropTypes.any,
filteredTreeNodes: PropTypes.any,
notFoundContent: PropTypes.string,
ariaId: PropTypes.string,
switcherIcon: PropTypes.any,
// HOC
renderSearch: PropTypes.func,
// onTreeExpanded: PropTypes.func,
__propsSymbol__: PropTypes.any,
},
inject: {
vcTreeSelect: { default: {}},
},
watch: {
__propsSymbol__ () {
const state = getDerivedStateFromProps(this.$props, this.$data)
this.setState(state)
},
},
data () {
warning(this.$props.__propsSymbol__, 'must pass __propsSymbol__')
const {
treeDefaultExpandAll, treeDefaultExpandedKeys,
keyEntities,
} = this.$props
// TODO: make `expandedKeyList` control
let expandedKeyList = treeDefaultExpandedKeys
if (treeDefaultExpandAll) {
expandedKeyList = Object.keys(keyEntities)
}
const state = {
_keyList: [],
_expandedKeyList: expandedKeyList,
// Cache `expandedKeyList` when tree is in filter. This is used in `getDerivedStateFromProps`
_cachedExpandedKeyList: [], // eslint-disable-line react/no-unused-state
_loadedKeys: [],
_prevProps: {},
}
return {
...state,
...getDerivedStateFromProps(this.$props, state),
}
},
methods: {
onTreeExpand (expandedKeyList) {
const { treeExpandedKeys } = this.$props
// Set uncontrolled state
if (!treeExpandedKeys) {
this.setState({ _expandedKeyList: expandedKeyList }, () => {
this.__emit('treeExpanded')
})
}
this.__emit('treeExpand', expandedKeyList)
},
onLoad (loadedKeys) {
this.setState({ _loadedKeys: loadedKeys })
},
/**
* Not pass `loadData` when searching. To avoid loop ajax call makes browser crash.
*/
getLoadData () {
const { loadData, searchValue } = this.$props
if (searchValue) return null
return loadData
},
/**
* This method pass to Tree component which is used for add filtered class
* in TreeNode > li
*/
filterTreeNode (treeNode) {
const { upperSearchValue, treeNodeFilterProp } = this.$props
const filterVal = treeNode[treeNodeFilterProp]
if (typeof filterVal === 'string') {
return upperSearchValue && (filterVal).toUpperCase().indexOf(upperSearchValue) !== -1
}
return false
},
renderNotFound () {
const { prefixCls, notFoundContent } = this.$props
return (
<span class={`${prefixCls}-not-found`}>
{notFoundContent}
</span>
)
},
},
render () {
const { _keyList: keyList, _expandedKeyList: expandedKeyList, _loadedKeys: loadedKeys } = this.$data
const {
prefixCls,
treeNodes, filteredTreeNodes,
treeIcon, treeLine, treeCheckable, treeCheckStrictly, multiple,
ariaId,
renderSearch,
switcherIcon,
searchHalfCheckedKeys,
} = this.$props
const { vcTreeSelect: {
onPopupKeyDown,
onTreeNodeSelect,
onTreeNodeCheck,
}} = this
const loadData = this.getLoadData()
const treeProps = {}
if (treeCheckable) {
treeProps.checkedKeys = keyList
} else {
treeProps.selectedKeys = keyList
}
let $notFound
let $treeNodes
if (filteredTreeNodes) {
if (filteredTreeNodes.length) {
treeProps.checkStrictly = true
$treeNodes = filteredTreeNodes
// Fill halfCheckedKeys
if (treeCheckable && !treeCheckStrictly) {
treeProps.checkedKeys = {
checked: keyList,
halfChecked: searchHalfCheckedKeys,
}
}
} else {
$notFound = this.renderNotFound()
}
} else if (!treeNodes.length) {
$notFound = this.renderNotFound()
} else {
$treeNodes = treeNodes
}
let $tree
if ($notFound) {
$tree = $notFound
} else {
const treeAllProps = {
props: {
prefixCls: `${prefixCls}-tree`,
showIcon: treeIcon,
showLine: treeLine,
selectable: !treeCheckable,
checkable: treeCheckable,
checkStrictly: treeCheckStrictly,
multiple: multiple,
loadData: loadData,
loadedKeys: loadedKeys,
expandedKeys: expandedKeyList,
filterTreeNode: this.filterTreeNode,
switcherIcon: switcherIcon,
...treeProps,
__propsSymbol__: Symbol(),
children: $treeNodes,
},
on: {
select: onTreeNodeSelect,
check: onTreeNodeCheck,
expand: this.onTreeExpand,
load: this.onLoad,
},
}
$tree = (
<Tree
{...treeAllProps}
/>
)
}
return (
<div
role='listbox'
id={ariaId}
onKeydown={onPopupKeyDown}
tabIndex={-1}
>
{renderSearch ? renderSearch() : null}
{$tree}
</div>
)
},
}
export default BasePopup

View File

@ -0,0 +1,187 @@
/**
* Input Box is in different position for different mode.
* This not the same design as `Select` cause it's followed by antd 0.x `Select`.
* We will not follow the new design immediately since antd 3.x is already released.
*
* So this file named as Selector to avoid confuse.
*/
import { createRef } from '../util'
import PropTypes from '../../../_util/vue-types'
import classNames from 'classnames'
import { initDefaultProps, getComponentFromProp } from '../../../_util/props-util'
import BaseMixin from '../../../_util/BaseMixin'
export const selectorPropTypes = () => ({
prefixCls: PropTypes.string,
className: PropTypes.string,
open: PropTypes.bool,
valueList: PropTypes.array, // Name as valueList to diff the single value
allowClear: PropTypes.bool,
showArrow: PropTypes.bool,
// onClick: PropTypes.func,
// onBlur: PropTypes.func,
// onFocus: PropTypes.func,
removeSelected: PropTypes.func,
choiceTransitionName: PropTypes.string,
// Pass by component
ariaId: PropTypes.string,
inputIcon: PropTypes.any,
clearIcon: PropTypes.any,
removeIcon: PropTypes.any,
selectorValueList: PropTypes.array,
placeholder: PropTypes.any,
disabled: PropTypes.bool,
focused: PropTypes.bool,
})
function noop () {}
export default function (modeName) {
const BaseSelector = {
name: 'BaseSelector',
mixins: [BaseMixin],
props: initDefaultProps({
...selectorPropTypes(),
// Pass by HOC
renderSelection: PropTypes.func.isRequired,
renderPlaceholder: PropTypes.func,
tabIndex: PropTypes.number,
}, {
tabIndex: 0,
}),
inject: {
vcTreeSelect: { default: {}},
},
created () {
this.domRef = createRef()
},
methods: {
onFocus (e) {
const { focused } = this.$props
const { vcTreeSelect: { onSelectorFocus }} = this
if (!focused) {
onSelectorFocus()
}
this.__emit('focus', e)
},
onBlur (e) {
const { vcTreeSelect: { onSelectorBlur }} = this
// TODO: Not trigger when is inner component get focused
onSelectorBlur()
this.__emit('blur', e)
},
focus () {
this.domRef.current.focus()
},
blur () {
this.domRef.current.blur()
},
renderClear () {
const { prefixCls, allowClear, valueList } = this.$props
const { vcTreeSelect: { onSelectorClear }} = this
if (!allowClear || !valueList.length || !valueList[0].value) {
return null
}
const clearIcon = getComponentFromProp(this, 'clearIcon')
return (
<span
key='clear'
class={`${prefixCls}-selection__clear`}
onClick={onSelectorClear}
>
{clearIcon}
</span>
)
},
renderArrow () {
const { prefixCls, showArrow } = this.$props
if (!showArrow) {
return null
}
const inputIcon = getComponentFromProp(this, 'inputIcon')
return (
<span
key='arrow'
class={`${prefixCls}-arrow`}
style={{ outline: 'none' }}
>
{inputIcon}
</span>
)
},
},
render () {
const {
prefixCls, className, style,
open, focused, disabled, allowClear,
ariaId,
renderSelection, renderPlaceholder,
tabIndex,
} = this.$props
const { vcTreeSelect: { onSelectorKeyDown }, $listeners } = this
let myTabIndex = tabIndex
if (disabled) {
myTabIndex = null
}
return (
<span
style={style}
onClick={$listeners.click || noop}
class={classNames(
className,
prefixCls,
{
[`${prefixCls}-open`]: open,
[`${prefixCls}-focused`]: open || focused,
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-enabled`]: !disabled,
[`${prefixCls}-allow-clear`]: allowClear,
}
)}
{...{
directives: [{
name: 'ant-ref',
value: this.domRef,
}],
}}
role='combobox'
aria-expanded={open}
aria-owns={open ? ariaId : undefined}
aria-controls={open ? ariaId : undefined}
aria-haspopup='listbox'
aria-disabled={disabled}
tabIndex={myTabIndex}
onFocus={this.onFocus}
onBlur={this.onBlur}
onKeydown={onSelectorKeyDown}
>
<span
key='selection'
class={classNames(
`${prefixCls}-selection`,
`${prefixCls}-selection--${modeName}`
)}
>
{renderSelection()}
{this.renderClear()}
{this.renderArrow()}
{renderPlaceholder && renderPlaceholder()}
</span>
</span>
)
},
}
return BaseSelector
}

View File

@ -0,0 +1,3 @@
import BasePopup from '../Base/BasePopup'
export default BasePopup

View File

@ -0,0 +1,80 @@
import PropTypes from '../../../_util/vue-types'
import BasePopup from '../Base/BasePopup'
import SearchInput from '../SearchInput'
import { createRef } from '../util'
const SinglePopup = {
name: 'SinglePopup',
props: {
...BasePopup.props,
...SearchInput.props,
searchValue: PropTypes.string,
showSearch: PropTypes.bool,
dropdownPrefixCls: PropTypes.string,
disabled: PropTypes.bool,
searchPlaceholder: PropTypes.string,
},
created () {
this.inputRef = createRef()
},
methods: {
onPlaceholderClick () {
this.inputRef.current.focus()
},
_renderPlaceholder () {
const { searchPlaceholder, searchValue, prefixCls } = this.$props
if (!searchPlaceholder) {
return null
}
return (
<span
style={{
display: searchValue ? 'none' : 'block',
}}
onClick={this.onPlaceholderClick}
class={`${prefixCls}-search__field__placeholder`}
>
{searchPlaceholder}
</span>
)
},
_renderSearch () {
const { showSearch, dropdownPrefixCls } = this.$props
if (!showSearch) {
return null
}
return (
<span class={`${dropdownPrefixCls}-search`}>
<SearchInput
{...{
props: { ...this.$props, renderPlaceholder: this._renderPlaceholder },
on: this.$listeners,
directives: [{
name: 'ant-ref',
value: this.inputRef,
}],
}}
/>
</span>
)
},
},
render () {
return (
<BasePopup
{...{
props: { ...this.$props, renderSearch: this._renderSearch, __propsSymbol__: Symbol() },
on: this.$listeners,
}}
/>
)
},
}
export default SinglePopup

View File

@ -1,62 +1,46 @@
import PropTypes from '../../_util/vue-types'
import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies'
import { isLabelInValue } from './util'
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,
treeDefaultExpandedKeys: PropTypes.arrayOf(String),
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,
getPopupContainer: PropTypes.func,
switcherIcon: PropTypes.func,
inputIcon: PropTypes.any,
removeIcon: PropTypes.any,
clearIcon: PropTypes.any,
const internalValProp = PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
])
export function genArrProps (propType) {
return PropTypes.oneOfType([
propType,
PropTypes.arrayOf(propType),
])
}
/**
* Origin code check `multiple` is true when `treeCheckStrictly` & `labelInValue`.
* But in process logic is already cover to array.
* Check array is not necessary. Let's simplify this check logic.
*/
export function valueProp (...args) {
const [props, propName, Component] = args
if (isLabelInValue(props)) {
const err = genArrProps(PropTypes.shape({
label: PropTypes.node,
value: internalValProp,
}).loose)(...args)
if (err) {
return new Error(
`Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
`You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.`
)
}
return null
}
const err = genArrProps(internalValProp)(...args)
if (err) {
return new Error(
`Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
`You should use string or [string] instead.`
)
}
return null
}

View File

@ -0,0 +1,127 @@
/**
* Since search box is in different position with different mode.
* - Single: in the popup box
* - multiple: in the selector
* Move the code as a SearchInput for easy management.
*/
import PropTypes from '../../_util/vue-types'
import { createRef } from './util'
const SearchInput = {
name: 'SearchInput',
props: {
open: PropTypes.bool,
searchValue: PropTypes.string,
prefixCls: PropTypes.string,
disabled: PropTypes.bool,
renderPlaceholder: PropTypes.func,
needAlign: PropTypes.bool,
ariaId: PropTypes.string,
},
inject: {
vcTreeSelect: { default: {}},
},
created () {
this.inputRef = createRef()
this.mirrorInputRef = createRef()
this.prevProps = { ...this.$props }
},
mounted () {
this.$nextTick(() => {
const { open, needAlign } = this.$props
if (needAlign) {
this.alignInputWidth()
}
if (open) {
this.focus(true)
}
})
},
updated () {
const { open, searchValue, needAlign } = this.$props
const { prevProps } = this
this.$nextTick(() => {
if (open && prevProps.open !== open) {
this.focus()
}
if (needAlign && searchValue !== prevProps.searchValue) {
this.alignInputWidth()
}
this.prevProps = { ...this.$props }
})
},
methods: {
/**
* `scrollWidth` is not correct in IE, do the workaround.
* ref: https://github.com/react-component/tree-select/issues/65
* clientWidth 0 when mounted in vue. why?
*/
alignInputWidth () {
this.inputRef.current.style.width =
`${this.mirrorInputRef.current.clientWidth || this.mirrorInputRef.current.offsetWidth}px`
},
/**
* Need additional timeout for focus cause parent dom is not ready when didMount trigger
*/
focus (isDidMount) {
if (this.inputRef.current) {
this.inputRef.current.focus()
if (isDidMount) {
setTimeout(() => {
this.inputRef.current.focus()
}, 0)
}
}
},
blur () {
if (this.inputRef.current) {
this.inputRef.current.blur()
}
},
},
render () {
const { searchValue, prefixCls, disabled, renderPlaceholder, open, ariaId } = this.$props
const { vcTreeSelect: {
onSearchInputChange, onSearchInputKeyDown,
}} = this
return (
<span class={`${prefixCls}-search__field__wrap`}>
<input
type='text'
{...{ directives: [{
name: 'ant-ref',
value: this.inputRef,
}] }}
onInput={onSearchInputChange}
onKeydown={onSearchInputKeyDown}
value={searchValue}
disabled={disabled}
class={`${prefixCls}-search__field`}
aria-label='filter select'
aria-autocomplete='list'
aria-controls={open ? ariaId : undefined}
aria-multiline='false'
/>
<span
{...{ directives: [{
name: 'ant-ref',
value: this.mirrorInputRef,
}] }}
class={`${prefixCls}-search__field__mirror`}
>
{searchValue}&nbsp;
</span>
{renderPlaceholder ? renderPlaceholder() : null}
</span>
)
},
}
export default SearchInput

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
import { TreeNode } from '../../vc-tree'
/**
* SelectNode wrapped the tree node.
* Let's use SelectNode instead of TreeNode
* since TreeNode is so confuse here.
*/
export default {
functional: true,
name: 'SelectNode',
isTreeNode: true,
props: TreeNode.props,
render (h, context) {
const { props, slots, listeners, data } = context
const $slots = slots()
const children = $slots.default
delete $slots.default
const treeNodeProps = {
...data, on: { ...listeners, ...data.nativeOn }, props,
}
const slotsKey = Object.keys($slots)
return <TreeNode {...treeNodeProps}>
{children}
{slotsKey.length ? slotsKey.map(name => {
return <template slot={name}>{$slots[name]}</template>
}) : null}
</TreeNode>
},
}

View File

@ -1,18 +1,8 @@
import PropTypes from '../../_util/vue-types'
import classnames from 'classnames'
import Trigger from '../../vc-trigger'
import Tree, { TreeNode } from '../../vc-tree'
import { SelectPropTypes } from './PropTypes'
import BaseMixin from '../../_util/BaseMixin'
import {
loopAllChildren,
flatToHierarchy,
getValuePropValue,
labelCompatible,
} from './util'
import { cloneElement } from '../../_util/vnode'
import { getSlotOptions, getKey, getAllProps, getComponentFromProp } from '../../_util/props-util'
import Trigger from '../../vc-trigger'
import { createRef } from './util'
import classNames from 'classnames'
const BUILT_IN_PLACEMENTS = {
bottomLeft: {
@ -22,6 +12,7 @@ const BUILT_IN_PLACEMENTS = {
adjustX: 0,
adjustY: 1,
},
ignoreShake: true,
},
topLeft: {
points: ['bl', 'tl'],
@ -30,335 +21,94 @@ const BUILT_IN_PLACEMENTS = {
adjustX: 0,
adjustY: 1,
},
ignoreShake: true,
},
}
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,
// Pass by outside user props
disabled: PropTypes.bool,
showSearch: PropTypes.bool,
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,
}
},
dropdownPopupAlign: PropTypes.object,
dropdownClassName: PropTypes.string,
dropdownStyle: PropTypes.object,
transitionName: PropTypes.string,
animation: PropTypes.string,
getPopupContainer: PropTypes.func,
mounted () {
this.$nextTick(() => {
this.setDropdownWidth()
})
},
watch: {
inputValue (val) {
// set autoExpandParent to true
this.setState({
sExpandedKeys: [],
fireOnExpand: false,
})
},
},
dropdownMatchSelectWidth: PropTypes.bool,
updated () {
this.$nextTick(() => {
this.setDropdownWidth()
})
// Pass by Select
isMultiple: PropTypes.bool,
dropdownPrefixCls: PropTypes.string,
dropdownVisibleChange: PropTypes.func,
popupElement: PropTypes.node,
open: PropTypes.bool,
},
created () {
this.triggerRef = createRef()
},
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}`
const { transitionName, animation, dropdownPrefixCls } = this.$props
if (!transitionName && animation) {
return `${dropdownPrefixCls}-${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
forcePopupAlign () {
const $trigger = this.triggerRef.current
if ($trigger) {
$trigger.forcePopupAlign()
}
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),
switcherIcon: props.switcherIcon,
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);
const {
disabled, isMultiple,
dropdownPopupAlign, dropdownMatchSelectWidth, dropdownClassName,
dropdownStyle, dropdownVisibleChange, getPopupContainer,
dropdownPrefixCls, popupElement, open,
} = this.$props
if (props.inputValue) {
treeNodes = this.processTreeNode(treeNodes)
// TODO: [Legacy] Use new action when trigger fixed: https://github.com/react-component/trigger/pull/86
// When false do nothing with the width
// ref: https://github.com/ant-design/ant-design/issues/10927
let stretch
if (dropdownMatchSelectWidth !== false) {
stretch = dropdownMatchSelectWidth ? 'width' : 'minWidth'
}
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'
{...{ directives: [{
name: 'ant-ref',
value: this.triggerRef,
}] }}
action={disabled ? [] : ['click']}
popupPlacement='bottomLeft'
builtinPlacements={BUILT_IN_PLACEMENTS}
popupAlign={props.dropdownPopupAlign}
popupAlign={dropdownPopupAlign}
prefixCls={dropdownPrefixCls}
popupTransitionName={this.getDropdownTransitionName()}
onPopupVisibleChange={props.dropdownVisibleChange}
onPopupVisibleChange={dropdownVisibleChange}
popup={popupElement}
popupVisible={visible}
getPopupContainer={props.getPopupContainer}
popupClassName={classnames(popupClassName)}
popupStyle={popupStyle}
popupVisible={open}
getPopupContainer={getPopupContainer}
stretch={stretch}
popupClassName={classNames(
dropdownClassName,
{
[`${dropdownPrefixCls}--multiple`]: isMultiple,
[`${dropdownPrefixCls}--single`]: !isMultiple,
},
)}
popupStyle={dropdownStyle}
>
{this.$slots.default}
</Trigger>

View File

@ -0,0 +1,64 @@
import PropTypes from '../../../../_util/vue-types'
import {
toTitle,
UNSELECTABLE_ATTRIBUTE, UNSELECTABLE_STYLE,
} from '../../util'
import { getComponentFromProp } from '../../../../_util/props-util'
import BaseMixin from '../../../../_util/BaseMixin'
const Selection = {
mixins: [BaseMixin],
props: {
prefixCls: PropTypes.string,
maxTagTextLength: PropTypes.number,
// onRemove: PropTypes.func,
label: PropTypes.any,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
removeIcon: PropTypes.any,
},
methods: {
onRemove (event) {
const { value } = this.$props
this.__emit('remove', event, value)
event.stopPropagation()
},
},
render () {
const {
prefixCls, maxTagTextLength,
label, value,
} = this.$props
const { $listeners } = this
let content = label || value
if (maxTagTextLength && typeof content === 'string' && content.length > maxTagTextLength) {
content = `${content.slice(0, maxTagTextLength)}...`
}
return (
<li
style={UNSELECTABLE_STYLE}
{...{ attrs: UNSELECTABLE_ATTRIBUTE }}
role='menuitem'
class={`${prefixCls}-selection__choice`}
title={toTitle(label)}
>
{$listeners.remove &&
<span
class={`${prefixCls}-selection__choice__remove`}
onClick={this.onRemove}
>
{getComponentFromProp(this, 'removeIcon')}
</span>
}
<span class={`${prefixCls}-selection__choice__content`}>
{content}
</span>
</li>
)
},
}
export default Selection

View File

@ -0,0 +1,192 @@
import PropTypes from '../../../../_util/vue-types'
import { createRef } from '../../util'
import generateSelector, { selectorPropTypes } from '../../Base/BaseSelector'
import SearchInput from '../../SearchInput'
import Selection from './Selection'
import { getComponentFromProp } from '../../../../_util/props-util'
import getTransitionProps from '../../../../_util/getTransitionProps'
import BaseMixin from '../../../../_util/BaseMixin'
const TREE_SELECT_EMPTY_VALUE_KEY = 'RC_TREE_SELECT_EMPTY_VALUE_KEY'
const Selector = generateSelector('multiple')
// export const multipleSelectorContextTypes = {
// onMultipleSelectorRemove: PropTypes.func.isRequired,
// }
const MultipleSelector = {
mixins: [BaseMixin],
props: {
...selectorPropTypes(),
...SearchInput.props,
selectorValueList: PropTypes.array,
disabled: PropTypes.bool,
searchValue: PropTypes.string,
labelInValue: PropTypes.bool,
maxTagCount: PropTypes.number,
maxTagPlaceholder: PropTypes.any,
// onChoiceAnimationLeave: PropTypes.func,
},
inject: {
vcTreeSelect: { default: {}},
},
created () {
this.inputRef = createRef()
},
methods: {
onPlaceholderClick () {
this.inputRef.current.focus()
},
focus () {
this.inputRef.current.focus()
},
blur () {
this.inputRef.current.blur()
},
_renderPlaceholder () {
const {
prefixCls,
placeholder, searchPlaceholder,
searchValue, selectorValueList,
} = this.$props
const currentPlaceholder = placeholder || searchPlaceholder
if (!currentPlaceholder) return null
const hidden = searchValue || selectorValueList.length
// [Legacy] Not remove the placeholder
return (
<span
style={{
display: hidden ? 'none' : 'block',
}}
onClick={this.onPlaceholderClick}
class={`${prefixCls}-search__field__placeholder`}
>
{currentPlaceholder}
</span>
)
},
onChoiceAnimationLeave (...args) {
this.__emit('choiceAnimationLeave', ...args)
},
renderSelection () {
const {
selectorValueList, choiceTransitionName, prefixCls,
labelInValue, maxTagCount,
} = this.$props
const { vcTreeSelect: { onMultipleSelectorRemove }, $listeners, $slots } = this
// Check if `maxTagCount` is set
let myValueList = selectorValueList
if (maxTagCount >= 0) {
myValueList = selectorValueList.slice(0, maxTagCount)
}
// Selector node list
const selectedValueNodes = myValueList.map(({ label, value }) => (
<Selection
{...{
props: {
...this.$props,
label,
value,
},
on: { ...$listeners, remove: onMultipleSelectorRemove },
}}
key={value || TREE_SELECT_EMPTY_VALUE_KEY}
>{$slots.default}</Selection>
))
// Rest node count
if (maxTagCount >= 0 && maxTagCount < selectorValueList.length) {
let content = `+ ${selectorValueList.length - maxTagCount} ...`
const maxTagPlaceholder = getComponentFromProp(this, 'maxTagPlaceholder', {}, false)
if (typeof maxTagPlaceholder === 'string') {
content = maxTagPlaceholder
} else if (typeof maxTagPlaceholder === 'function') {
const restValueList = selectorValueList.slice(maxTagCount)
content = maxTagPlaceholder(
labelInValue ? restValueList : restValueList.map(({ value }) => value)
)
}
const restNodeSelect = (
<Selection
{...{
props: {
...this.$props,
label: content,
value: null,
},
on: $listeners,
}}
key='rc-tree-select-internal-max-tag-counter'
>{$slots.default}</Selection>
)
selectedValueNodes.push(restNodeSelect)
}
selectedValueNodes.push(<li
class={`${prefixCls}-search ${prefixCls}-search--inline`}
key='__input'
>
<SearchInput {...{
props: {
...this.$props,
needAlign: true,
},
on: $listeners,
directives: [{
name: 'ant-ref',
value: this.inputRef,
}],
}}>{$slots.default}</SearchInput>
</li>)
const className = `${prefixCls}-selection__rendered`
if (choiceTransitionName) {
const transitionProps = getTransitionProps(choiceTransitionName, {
tag: 'ul',
afterLeave: this.onChoiceAnimationLeave,
})
return (<transition-group
class={className}
{...transitionProps}
>
{selectedValueNodes}
</transition-group>)
}
return (
<ul class={className} role='menubar'>
{selectedValueNodes}
</ul>
)
},
},
render () {
const { $listeners, $slots } = this
return (
<Selector
{...{
props: {
...this.$props,
tabIndex: -1,
showArrow: false,
renderSelection: this.renderSelection,
renderPlaceholder: this._renderPlaceholder,
},
on: $listeners,
}}
>{$slots.default}</Selector>
)
},
}
export default MultipleSelector

View File

@ -0,0 +1,75 @@
import generateSelector, { selectorPropTypes } from '../Base/BaseSelector'
import { toTitle } from '../util'
import { getOptionProps } from '../../../_util/props-util'
import { createRef } from '../util'
const Selector = generateSelector('single')
const SingleSelector = {
name: 'SingleSelector',
props: selectorPropTypes(),
created () {
this.selectorRef = createRef()
},
methods: {
focus () {
this.selectorRef.current.focus()
},
blur () {
this.selectorRef.current.blur()
},
renderSelection () {
const { selectorValueList, placeholder, prefixCls } = this.$props
let innerNode
if (selectorValueList.length) {
const { label, value } = selectorValueList[0]
innerNode = (
<span
key='value'
title={toTitle(label)}
class={`${prefixCls}-selection-selected-value`}
>
{label || value}
</span>
)
} else {
innerNode = (
<span
key='placeholder'
class={`${prefixCls}-selection__placeholder`}
>
{placeholder}
</span>
)
}
return (
<span class={`${prefixCls}-selection__rendered`}>
{innerNode}
</span>
)
},
},
render () {
const props = {
props: {
...getOptionProps(this),
renderSelection: this.renderSelection,
},
on: this.$listeners,
directives: [{
name: 'ant-ref',
value: this.selectorRef,
}],
}
return (
<Selector
{...props}
/>
)
},
}
export default SingleSelector

View File

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

View File

@ -1,26 +1,7 @@
// 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
import Select from './Select'
import SelectNode from './SelectNode'
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 }
export { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './strategies'
export const TreeNode = SelectNode
export default Select

View File

@ -0,0 +1,46 @@
import PropTypes from '../../_util/vue-types'
import { isLabelInValue } from './util'
const internalValProp = PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
])
export function genArrProps (propType) {
return PropTypes.oneOfType([
propType,
PropTypes.arrayOf(propType),
])
}
/**
* Origin code check `multiple` is true when `treeCheckStrictly` & `labelInValue`.
* But in process logic is already cover to array.
* Check array is not necessary. Let's simplify this check logic.
*/
export function valueProp (...args) {
const [props, propName, Component] = args
if (isLabelInValue(props)) {
const err = genArrProps(PropTypes.shape({
label: PropTypes.node,
value: internalValProp,
}).loose)(...args)
if (err) {
return new Error(
`Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
`You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.`
)
}
return null
}
const err = genArrProps(internalValProp)(...args)
if (err) {
return new Error(
`Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
`You should use string or [string] instead.`
)
}
return null
}

View File

@ -1,5 +1,17 @@
import { getPropsData, getAllProps, getKey, getAttrs, getSlotOptions, getSlots } from '../../_util/props-util'
import { cloneVNodes, cloneElement } from '../../_util/vnode'
import warning from 'warning'
import omit from 'omit.js'
import {
convertDataToTree as vcConvertDataToTree,
convertTreeToEntities as vcConvertTreeToEntities,
conductCheck as rcConductCheck,
} from '../../vc-tree/src/util'
import SelectNode from './SelectNode'
import { SHOW_CHILD, SHOW_PARENT } from './strategies'
import { getSlots, getPropsData } from '../../_util/props-util'
let warnDeprecatedLabel = false
// =================== MISC ====================
export function toTitle (title) {
if (typeof title === 'string') {
return title
@ -7,58 +19,20 @@ export function toTitle (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 toArray (data) {
if (!data) return []
return Array.isArray(data) ? data : [data]
}
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 createRef () {
const func = function setRef (node) {
func.current = node
}
return func
}
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()
}
// =============== Legacy ===============
export const UNSELECTABLE_STYLE = {
userSelect: 'none',
WebkitUserSelect: 'none',
@ -68,502 +42,389 @@ export const UNSELECTABLE_ATTRIBUTE = {
unselectable: 'unselectable',
}
export function labelCompatible (prop) {
let newProp = prop
if (newProp === 'label') {
newProp = 'title'
/**
* Convert position list to hierarchy structure.
* This is little hack since use '-' to split the position.
*/
export function flatToHierarchy (positionList) {
if (!positionList.length) {
return []
}
return newProp
}
export function isInclude (smallArray, bigArray) {
// attention: [0,0,1] [0,0,10]
return smallArray.every((ii, i) => {
return ii === bigArray[i]
})
}
const entrances = {}
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;
// Prepare the position map
const posMap = {}
const parsedList = positionList.slice().map(entity => {
const clone = {
...entity,
fields: entity.pos.split('-'),
}
});
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;
}
*/
delete clone.children
return clone
})
function getChildrenlength (children) {
let len = 1
if (Array.isArray(children)) {
len = children.length
}
return len
parsedList.forEach((entity) => {
posMap[entity.pos] = entity
})
parsedList.sort((a, b) => {
return a.fields.length - b.fields.length
})
// Create the hierarchy
parsedList.forEach((entity) => {
const parentPos = entity.fields.slice(0, -1).join('-')
const parentEntity = posMap[parentPos]
if (!parentEntity) {
entrances[entity.pos] = entity
} else {
parentEntity.children = parentEntity.children || []
parentEntity.children.push(entity)
}
// Some time position list provide `key`, we don't need it
delete entity.key
delete entity.fields
})
return Object.keys(entrances).map(key => entrances[key])
}
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
// =============== Accessibility ===============
let ariaId = 0
export function resetAriaId () {
ariaId = 0
}
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)
export function generateAriaId (prefix) {
ariaId += 1
return `${prefix}_${ariaId}`
}
export function isLabelInValue (props) {
const { treeCheckable, treeCheckStrictly, labelInValue } = props
if (treeCheckable && treeCheckStrictly) {
return true
}
return labelInValue || false
}
// =================== Tree ====================
export function parseSimpleTreeData (treeData, { id, pId, rootPId }) {
const keyNodes = {}
const rootNodeList = []
// Fill in the map
const nodeList = treeData.map((node) => {
const clone = { ...node }
const key = clone[id]
keyNodes[key] = clone
clone.key = clone.key || key
return clone
})
// Connect tree
nodeList.forEach((node) => {
const parentKey = node[pId]
const parent = keyNodes[parentKey]
// Fill parent
if (parent) {
parent.children = parent.children || []
parent.children.push(node)
}
// Fill root tree node
if (parentKey === rootPId || (!parent && rootPId === null)) {
rootNodeList.push(node)
}
})
return newChilds
return rootNodeList
}
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)
/**
* Detect if position has relation.
* e.g. 1-2 related with 1-2-3
* e.g. 1-3-2 related with 1
* e.g. 1-2 not related with 1-21
*/
export function isPosRelated (pos1, pos2) {
const fields1 = pos1.split('-')
const fields2 = pos2.split('-')
const minLen = Math.min(fields1.length, fields2.length)
for (let i = 0; i < minLen; i += 1) {
if (fields1[i] !== fields2[i]) {
return false
}
}
return true
}
/**
* This function is only used on treeNode check (none treeCheckStrictly but has searchInput).
* We convert entity to { node, pos, children } format.
* This is legacy bug but we still need to do with it.
* @param entity
*/
export function cleanEntity ({ node, pos, children }) {
const instance = {
node,
pos,
}
if (children) {
instance.children = children.map(cleanEntity)
}
return instance
}
/**
* Get a filtered TreeNode list by provided treeNodes.
* [Legacy] Since `Tree` use `key` as map but `key` will changed by React,
* we have to convert `treeNodes > data > treeNodes` to keep the key.
* Such performance hungry!
*/
export function getFilterTree (h, treeNodes, searchValue, filterFunc, valueEntities) {
if (!searchValue) {
return null
}
function mapFilteredNodeToData (node) {
if (!node) return null
let match = false
if (filterFunc(searchValue, node)) {
match = true
}
const $slots = getSlots(node)
const children = ($slots.default || []).map(mapFilteredNodeToData).filter(n => n)
delete $slots.default
const slotsKey = Object.keys($slots)
if (children.length || match) {
return (
<SelectNode
{...node.data}
key={valueEntities[getPropsData(node).value].key}
>
{children}
{slotsKey.length ? slotsKey.map(name => {
return <template slot={name}>{$slots[name][0].tag === 'template' ? $slots[name][0].children : $slots[name]}</template>
}) : null}
</SelectNode>
)
}
return null
}
return treeNodes.map(mapFilteredNodeToData).filter(node => node)
}
// =================== Value ===================
/**
* Convert value to array format to make logic simplify.
*/
export function formatInternalValue (value, props) {
const valueList = toArray(value)
// Parse label in value
if (isLabelInValue(props)) {
return valueList.map((val) => {
if (typeof val !== 'object' || !val) {
return {
value: '',
label: '',
}
}
return val
})
}
loop(filterChild(childs), 0, parent)
return valueList.map(val => ({
value: val,
}))
}
// 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
export function getLabel (wrappedValue, entity, treeNodeLabelProp) {
if (wrappedValue.label) {
return wrappedValue.label
}
const hierarchyNodes = []
const levelObj = {}
arr.forEach((item) => {
if (!item.pos) {
return
if (entity) {
const props = getPropsData(entity.node)
if (Object.keys(props).length) {
return props[treeNodeLabelProp]
}
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)
}
}
// Since value without entity will be in missValueList.
// This code will never reached, but we still need this in case.
return wrappedValue.value
}
/**
* Convert internal state `valueList` to user needed value list.
* This will return an array list. You need check if is not multiple when return.
*
* `allCheckedNodes` is used for `treeCheckStrictly`
*/
export function formatSelectorValue (valueList, props, valueEntities) {
const {
treeNodeLabelProp,
treeCheckable, treeCheckStrictly, showCheckedStrategy,
} = props
// Will hide some value if `showCheckedStrategy` is set
if (treeCheckable && !treeCheckStrictly) {
const values = {}
valueList.forEach((wrappedValue) => {
values[wrappedValue.value] = wrappedValue
})
const hierarchyList = flatToHierarchy(valueList.map(({ value }) => valueEntities[value]))
if (showCheckedStrategy === SHOW_PARENT) {
// Only get the parent checked value
return hierarchyList.map(({ node }) => {
const value = getPropsData(node).value
return {
label: getLabel(values[value], valueEntities[value], treeNodeLabelProp),
value,
}
})
} else if (showCheckedStrategy === SHOW_CHILD) {
// Only get the children checked value
const targetValueList = []
// Find the leaf children
const traverse = ({ node, children }) => {
const value = getPropsData(node).value
if (!children || children.length === 0) {
targetValueList.push({
label: getLabel(values[value], valueEntities[value], treeNodeLabelProp),
value,
})
return
}
children.forEach((entity) => {
traverse(entity)
})
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)
}
hierarchyList.forEach((entity) => {
traverse(entity)
})
return targetValueList
}
}
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 valueList.map(wrappedValue => ({
label: getLabel(wrappedValue, valueEntities[wrappedValue.value], treeNodeLabelProp),
value: wrappedValue.value,
}))
}
/**
* Use `rc-tree` convertDataToTree to convert treeData to TreeNodes.
* This will change the label to title value
*/
function processProps (props) {
const { title, label, key, value, class: cls, style, on = {}} = props
const p = {
props: omit(props, ['on', 'key', 'class', 'className', 'style']),
on,
class: cls || props.className,
style: style,
key: typeof key === 'number' ? String(key) : (key || value),
}
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
// Warning user not to use deprecated label prop.
if (label && !title) {
if (!warnDeprecatedLabel) {
warning(
false,
'\'label\' in treeData is deprecated. Please use \'title\' instead.'
)
warnDeprecatedLabel = true
}
})
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)
p.props.title = label
}
// console.log(Date.now()-s, objKeys.length, checkIt);
return p
}
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)
}
})
export function convertDataToTree (h, treeData) {
return vcConvertDataToTree(h, treeData, { processProps })
}
/**
* Use `rc-tree` convertTreeToEntities for entities calculation.
* We have additional entities of `valueEntities`
*/
function initWrapper (wrapper) {
return {
halfCheckedKeys, checkedKeys, checkedNodes, treeNodesStates, checkedPositions,
...wrapper,
valueEntities: {},
}
}
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)
}
})
function processEntity (entity, wrapper) {
const value = getPropsData(entity.node).value
entity.value = value
handleCheckState(treeNodesStates, filterParentPosition(checkedPositions.sort()), true)
return getCheck(treeNodesStates, checkedPositions)
// This should be empty, or will get error message.
const currentEntity = wrapper.valueEntities[value]
if (currentEntity) {
warning(
false,
`Conflict! value of node '${entity.key}' (${value}) has already used by node '${currentEntity.key}'.`
)
}
wrapper.valueEntities[value] = entity
}
// 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
export function convertTreeToEntities (treeNodes) {
return vcConvertTreeToEntities(treeNodes, {
initWrapper,
processEntity,
})
}
function recursive (children, cb) {
children.forEach(item => {
cb(item)
if (item.children) {
recursive(item.children, cb)
/**
* https://github.com/ant-design/ant-design/issues/13328
* We need calculate the half check key when searchValue is set.
*/
// TODO: This logic may better move to rc-tree
export function getHalfCheckedKeys (valueList, valueEntities) {
const values = {}
// Fill checked keys
valueList.forEach(({ value }) => {
values[value] = false
})
// Fill half checked keys
valueList.forEach(({ value }) => {
let current = valueEntities[value]
while (current && current.parent) {
const parentValue = current.parent.value
if (parentValue in values) break
values[parentValue] = true
current = current.parent
}
})
// Get half keys
return Object.keys(values).filter(value => values[value]).map(value => valueEntities[value].key)
}
// 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]
}
export const conductCheck = rcConductCheck

View File

@ -101,6 +101,8 @@ const Tree = {
}),
data () {
warning(this.$props.__propsSymbol__, 'must pass __propsSymbol__')
warning(this.$props.children, 'please children prop replace slots.default')
this.needSyncKeys = {}
const state = {
_posEntities: {},
@ -119,12 +121,6 @@ const Tree = {
}
return {
...state,
// ...this.getSyncProps(props),
// dragOverNodeKey: '',
// dropPosition: null,
// dragNodesKeys: [],
// sLoadedKeys: [],
// sLoadingKeys: [],
...this.getDerivedStateFromProps(getOptionProps(this), state),
}
},
@ -478,7 +474,7 @@ const Tree = {
event: 'load',
node: treeNode,
}
this.__emit('load', eventObj)
this.__emit('load', newLoadedKeys, eventObj)
this.setUncontrolledState({
_loadedKeys: newLoadedKeys,
})
@ -593,7 +589,6 @@ const Tree = {
return cloneElement(child, {
props: {
key,
eventKey: key,
expanded: expandedKeys.indexOf(key) !== -1,
selected: selectedKeys.indexOf(key) !== -1,
@ -608,6 +603,7 @@ const Tree = {
dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1,
},
key,
})
},
},

View File

@ -46,6 +46,9 @@ const TreeNode = {
icon: PropTypes.any,
dataRef: PropTypes.object,
switcherIcon: PropTypes.any,
label: PropTypes.any,
value: PropTypes.any,
}, {}),
data () {

View File

@ -4,8 +4,10 @@ import Align from '../vc-align'
import PopupInner from './PopupInner'
import LazyRenderBox from './LazyRenderBox'
import animate from '../_util/css-animation'
import BaseMixin from '../_util/BaseMixin'
export default {
mixins: [BaseMixin],
props: {
visible: PropTypes.bool,
getClassNameFromAlign: PropTypes.func,
@ -165,7 +167,7 @@ export default {
// Delay force align to makes ui smooth
if (!stretchChecked) {
sizeStyle.visibility = 'hidden'
// sizeStyle.visibility = 'hidden'
setTimeout(() => {
if (this.$refs.alignInstance) {
this.$refs.alignInstance.forceAlign()