feat: update tree-select

pull/1845/head
tangjinzhou 2020-02-25 17:51:45 +08:00
parent 10b91894f5
commit 747d5496e7
27 changed files with 387 additions and 196 deletions

View File

@ -1,5 +1,5 @@
module.exports = {
dev: {
componentName: 'upload', // dev components
componentName: 'tree-select', // dev components
},
};

View File

@ -1,19 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
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 aria-label="icon: down" 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" focusable="false" 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/async.md correctly 1`] = `<span role="combobox" aria-haspopup="listbox" tabindex="0" class="ant-select ant-select-enabled" style="width: 100%;"><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 aria-label="icon: down" 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" focusable="false" 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: 100%;"><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 aria-label="icon: down" 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" focusable="false" 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 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" style="user-select: none;"><span class="ant-select-selection__choice__remove"><i aria-label="icon: close" 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" focusable="false" 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>
<span role="combobox" aria-haspopup="listbox" tabindex="-1" class="ant-select ant-select-enabled" style="width: 100%;"><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" style="user-select: none;"><span class="ant-select-selection__choice__remove"><i aria-label="icon: close" 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" focusable="false" 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 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>
<span role="combobox" aria-haspopup="listbox" tabindex="-1" class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 100%;"><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/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 slot="suffixIcon" aria-label="icon: smile" class="anticon anticon-smile"><svg viewBox="64 64 896 896" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" 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/suffix.md correctly 1`] = `<span role="combobox" aria-haspopup="listbox" tabindex="0" class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 100%;"><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 slot="suffixIcon" aria-label="icon: smile" class="anticon anticon-smile"><svg viewBox="64 64 896 896" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" 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 aria-label="icon: down" 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" focusable="false" 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/treeData.md correctly 1`] = `<span role="combobox" aria-haspopup="listbox" tabindex="0" class="ant-select ant-select-enabled" style="width: 100%;"><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 aria-label="icon: down" 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" focusable="false" 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

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

View File

@ -0,0 +1,69 @@
<cn>
#### 异步加载
异步加载树节点。
</cn>
<us>
#### Asynchronous loading
Asynchronous loading tree node.
</us>
```tpl
<template>
<a-tree-select
treeDataSimpleMode
style="width: 100%"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
:treeData="treeData"
placeholder="Please select"
:loadData="onLoadData"
v-model="value"
/>
</template>
<script>
export default {
data() {
return {
value: undefined,
treeData: [
{ id: 1, pId: 0, value: '1', title: 'Expand to load' },
{ id: 2, pId: 0, value: '2', title: 'Expand to load' },
{ id: 3, pId: 0, value: '3', title: 'Tree Node', isLeaf: true },
],
};
},
methods: {
genTreeNode(parentId, isLeaf = false) {
const random = Math.random()
.toString(36)
.substring(2, 6);
return {
id: random,
pId: parentId,
value: random,
title: isLeaf ? 'Tree Node' : 'Expand to load',
isLeaf,
};
},
onLoadData(treeNode) {
return new Promise(resolve => {
const { id } = treeNode.dataRef;
setTimeout(() => {
this.treeData = this.treeData.concat([
this.genTreeNode(id, false),
this.genTreeNode(id, true),
]);
resolve();
}, 300);
});
},
},
watch: {
value(value) {
console.log(value);
},
},
};
</script>
```

View File

@ -12,13 +12,12 @@ The most basic usage.
<template>
<a-tree-select
showSearch
style="width: 300px"
:value="value"
style="width: 100%"
v-model="value"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
placeholder="Please select"
allowClear
treeDefaultExpandAll
@change="onChange"
>
<a-tree-select-node value="parent 1" title="parent 1" key="0-1">
<a-tree-select-node value="parent 1-0" title="parent 1-0" key="0-1-1">
@ -42,12 +41,6 @@ The most basic usage.
value: undefined,
};
},
methods: {
onChange(value) {
console.log(value);
this.value = value;
},
},
};
</script>
```

View File

@ -11,10 +11,9 @@ Multiple and checkable.
```tpl
<template>
<a-tree-select
style="width: 300px"
style="width: 100%"
:treeData="treeData"
:value="value"
@change="onChange"
v-model="value"
treeCheckable
:showCheckedStrategy="SHOW_PARENT"
searchPlaceholder="Please select"
@ -70,12 +69,6 @@ Multiple and checkable.
SHOW_PARENT,
};
},
methods: {
onChange(value) {
console.log('onChange ', value);
this.value = value;
},
},
};
</script>
```

View File

@ -4,6 +4,7 @@ import Checkable from './checkable';
import Multiple from './multiple';
import TreeData from './treeData';
import Suffix from './suffix';
import Async from './async';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
@ -28,15 +29,32 @@ export default {
subtitle: '树选择',
type: 'Data Entry',
title: 'TreeSelect',
data() {
return {
show: true,
};
},
render() {
return (
<div>
<button
onClick={() => {
this.show = !this.show;
}}
>
show
</button>
<md cn={md.cn} us={md.us} />
<Basic />
<Checkable />
<Multiple />
<TreeData />
<Suffix />
{this.show ? (
<div>
<Basic />
<Checkable />
<Multiple />
<TreeData />
<Suffix />
<Async />
</div>
) : null}
<api>
<template slot="cn">
<CN />

View File

@ -12,7 +12,7 @@ Multiple selection usage.
<template>
<a-tree-select
showSearch
style="width: 300px"
style="width: 100%"
:value="value"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
placeholder="Please select"

View File

@ -12,13 +12,12 @@ The most basic usage.
<template>
<a-tree-select
showSearch
style="width: 300px"
:value="value"
style="width: 100%"
v-model="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">
@ -42,12 +41,6 @@ The most basic usage.
value: undefined,
};
},
methods: {
onChange(value) {
console.log(value);
this.value = value;
},
},
};
</script>
```

View File

@ -11,7 +11,7 @@ The tree structure can be populated using `treeData` property. This is a quick a
```tpl
<template>
<a-tree-select
style="width: 300px"
style="width: 100%"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
:treeData="treeData"
placeholder="Please select"

View File

@ -56,13 +56,20 @@
> We recommend you to use `treeData` rather than `TreeNode`, to avoid the trouble of manual construction.
| 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 |
| key | Required property, should be unique in the tree | string \| number | - |
| title | Content showed on the treeNodes | string\|slot | '---' |
| value | Will be treated as `treeNodeFilterProp` by default, should be unique in the tree | string | - |
| scopedSlots | When using treeNodes, you can use this property to configure the properties that support the slot, such as `scopedSlots: { title: 'XXX'}` | object | - |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| selectable | can be selected | boolean | true | |
| checkable | When Tree is checkable, set TreeNode display Checkbox or not | boolean | - | 1.5.0 |
| disableCheckbox | Disables the checkbox of the treeNode | boolean | false | |
| disabled | Disabled or not | boolean | false | |
| isLeaf | Leaf node or not | boolean | false | |
| key | Required property, should be unique in the tree | string \| number | - | |
| title | Content showed on the treeNodes | string\|slot | '---' | |
| value | Will be treated as `treeNodeFilterProp` by default, should be unique in the tree | string | - | |
| scopedSlots | When using treeNodes, you can use this property to configure the properties that support the slot, such as `scopedSlots: { title: 'XXX'}` | object | - | |
## FAQ
### How to get parent node in onChange?
We don't provide this since performance consideration.

View File

@ -7,7 +7,6 @@ import {
getOptionProps,
getComponentFromProp,
filterEmpty,
isValidElement,
getListeners,
} from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
@ -16,7 +15,6 @@ import Base from '../base';
export { TreeData, TreeSelectProps } from './interface';
import Icon from '../icon';
import omit from 'omit.js';
import { cloneElement } from '../_util/vnode';
const TreeSelect = {
TreeNode: { ...TreeNode, name: 'ATreeSelectNode' },
@ -39,6 +37,7 @@ const TreeSelect = {
created() {
warning(
this.multiple !== false || !this.treeCheckable,
'TreeSelect',
'`multiple` will alway be `true` when `treeCheckable` is true',
);
},
@ -102,6 +101,8 @@ const TreeSelect = {
const renderEmpty = this.configProvider.renderEmpty;
const notFoundContent = getComponentFromProp(this, 'notFoundContent');
const removeIcon = getComponentFromProp(this, 'removeIcon');
const clearIcon = getComponentFromProp(this, 'clearIcon');
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
const rest = omit(restProps, [
'inputIcon',
@ -121,27 +122,33 @@ const TreeSelect = {
[`${prefixCls}-sm`]: size === 'small',
};
// showSearch: single - false, multiple - true
let { showSearch } = restProps;
if (!('showSearch' in restProps)) {
showSearch = !!(restProps.multiple || restProps.treeCheckable);
}
let checkable = getComponentFromProp(this, 'treeCheckable');
if (checkable) {
checkable = <span class={`${prefixCls}-tree-checkbox-inner`} />;
}
const inputIcon = (suffixIcon &&
(isValidElement(suffixIcon) ? cloneElement(suffixIcon) : suffixIcon)) || (
<Icon type="down" class={`${prefixCls}-arrow-icon`} />
const inputIcon = suffixIcon || <Icon type="down" class={`${prefixCls}-arrow-icon`} />;
const finalRemoveIcon = removeIcon || <Icon type="close" class={`${prefixCls}-remove-icon`} />;
const finalClearIcon = clearIcon || (
<Icon type="close-circle" class={`${prefixCls}-clear-icon`} theme="filled" />
);
const removeIcon = <Icon type="close" class={`${prefixCls}-remove-icon`} />;
const clearIcon = <Icon type="close-circle" class={`${prefixCls}-clear-icon`} theme="filled" />;
const VcTreeSelectProps = {
props: Object.assign(
{
switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, nodeProps),
inputIcon,
removeIcon,
clearIcon,
removeIcon: finalRemoveIcon,
clearIcon: finalClearIcon,
...rest,
showSearch,
getPopupContainer: getPopupContainer || getContextPopupContainer,
dropdownClassName: classNames(dropdownClassName, `${prefixCls}-tree-dropdown`),
prefixCls,

View File

@ -56,13 +56,14 @@
> 建议使用 treeData 来代替 TreeNode免去手工构造麻烦
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| selectable | 是否可选 | boolean | true |
| disableCheckbox | 禁掉 checkbox | boolean | false |
| disabled | 是否禁用 | boolean | false |
| isLeaf | 是否是叶子节点 | boolean | false |
| key | 此项必须设置(其值在整个树范围内唯一) | string \| number | - |
| title | 树节点显示的内容 | string\|slot | '---' |
| value | 默认根据此属性值进行筛选(其值在整个树范围内唯一) | string | - |
| scopedSlots | 使用 treeData 时,可以通过该属性配置支持 slot 的属性,如 `scopedSlots: { title: 'XXX'}` | object | - |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| selectable | 是否可选 | boolean | true | |
| checkable | 当树为 checkable 时,设置独立节点是否展示 Checkbox | boolean | - | 1.5.0 |
| disableCheckbox | 禁掉 checkbox | boolean | false | |
| disabled | 是否禁用 | boolean | false | |
| isLeaf | 是否是叶子节点 | boolean | false | |
| key | 此项必须设置(其值在整个树范围内唯一) | string \| number | - | |
| title | 树节点显示的内容 | string\|slot | '---' | |
| value | 默认根据此属性值进行筛选(其值在整个树范围内唯一) | string | - | |
| scopedSlots | 使用 treeData 时,可以通过该属性配置支持 slot 的属性,如 `scopedSlots: { title: 'XXX'}` | object | - | |

View File

@ -1,5 +1,5 @@
// export this package's api
// base 2.5.4
// base 2.9.3
import Vue from 'vue';
import TreeSelect from './src';
import ref from 'vue-ref';

View File

@ -2,6 +2,7 @@ import warning from 'warning';
import PropTypes from '../../../_util/vue-types';
import { Tree } from '../../../vc-tree';
import BaseMixin from '../../../_util/BaseMixin';
import { createRef } from '../util';
// export const popupContextTypes = {
// onPopupKeyDown: PropTypes.func.isRequired,
@ -110,6 +111,7 @@ const BasePopup = {
},
},
data() {
this.treeRef = createRef();
warning(this.$props.__propsSymbol__, 'must pass __propsSymbol__');
const { treeDefaultExpandAll, treeDefaultExpandedKeys, keyEntities } = this.$props;
@ -150,6 +152,10 @@ const BasePopup = {
this.setState({ _loadedKeys: loadedKeys });
},
getTree() {
return this.treeRef.current;
},
/**
* Not pass `loadData` when searching. To avoid loop ajax call makes browser crash.
*/
@ -231,7 +237,7 @@ const BasePopup = {
} else {
$notFound = this.renderNotFound();
}
} else if (!treeNodes.length) {
} else if (!treeNodes || !treeNodes.length) {
$notFound = this.renderNotFound();
} else {
$treeNodes = treeNodes;
@ -265,6 +271,12 @@ const BasePopup = {
expand: this.onTreeExpand,
load: this.onLoad,
},
directives: [
{
name: 'ant-ref',
value: this.treeRef,
},
],
};
$tree = <Tree {...treeAllProps} />;
}

View File

@ -17,12 +17,16 @@ const SinglePopup = {
},
created() {
this.inputRef = createRef();
this.searchRef = createRef();
this.popupRef = createRef();
},
methods: {
onPlaceholderClick() {
this.inputRef.current.focus();
},
getTree() {
return this.popupRef.current && this.popupRef.current.getTree();
},
_renderPlaceholder() {
const { searchPlaceholder, searchValue, prefixCls } = this.$props;
@ -51,7 +55,17 @@ const SinglePopup = {
}
return (
<span class={`${dropdownPrefixCls}-search`}>
<span
class={`${dropdownPrefixCls}-search`}
{...{
directives: [
{
name: 'ant-ref',
value: this.searchRef,
},
],
}}
>
<SearchInput
{...{
props: { ...this.$props, renderPlaceholder: this._renderPlaceholder },
@ -74,6 +88,12 @@ const SinglePopup = {
{...{
props: { ...this.$props, renderSearch: this._renderSearch, __propsSymbol__: Symbol() },
on: getListeners(this),
directives: [
{
name: 'ant-ref',
value: this.popupRef,
},
],
}}
/>
);

View File

@ -21,6 +21,7 @@
import shallowEqual from 'shallowequal';
import raf from 'raf';
import scrollIntoView from 'dom-scroll-into-view';
import warning from 'warning';
import PropTypes from '../../_util/vue-types';
import KeyCode from '../../_util/KeyCode';
@ -48,6 +49,7 @@ import {
isLabelInValue,
getFilterTree,
cleanEntity,
findPopupContainer,
} from './util';
import SelectNode from './SelectNode';
import {
@ -222,6 +224,37 @@ const Select = {
this.forcePopupAlign();
});
},
'$data._open': function() {
this.$nextTick(() => {
const { prefixCls } = this.$props;
const { _selectorValueList: selectorValueList, _valueEntities: valueEntities } = this.$data;
const isMultiple = this.isMultiple();
// Scroll to value position, only need sync on single mode
if (!isMultiple && selectorValueList.length && this.popup) {
const { value } = selectorValueList[0];
const { domTreeNodes } = this.popup.getTree();
const { key } = valueEntities[value] || {};
const treeNode = domTreeNodes[key];
if (treeNode) {
const domNode = treeNode.$el;
raf(() => {
const popupNode = this.popup.$el;
const triggerContainer = findPopupContainer(popupNode, `${prefixCls}-dropdown`);
const searchNode = this.popup.searchRef.current;
if (domNode && triggerContainer && searchNode) {
scrollIntoView(domNode, triggerContainer, {
onlyScrollIfNeeded: true,
offsetTop: searchNode.offsetHeight,
});
}
});
}
}
});
},
},
mounted() {
this.$nextTick(() => {
@ -342,9 +375,11 @@ const Select = {
}
// Get key by value
const valueLabels = {};
latestValueList.forEach(wrapperValue => {
const { value } = wrapperValue;
const { value, label } = wrapperValue;
const entity = (newState._valueEntities || prevState._valueEntities)[value];
valueLabels[value] = label;
if (entity) {
keyList.push(entity.key);
@ -366,9 +401,19 @@ const Select = {
);
// Format value list again for internal usage
newState._valueList = checkedKeys.map(key => ({
value: (newState._keyEntities || prevState._keyEntities).get(key).value,
}));
newState._valueList = checkedKeys.map(key => {
const val = (newState._keyEntities || prevState._keyEntities).get(key).value;
const wrappedValue = {
value: val,
};
if (valueLabels[val] !== undefined) {
wrappedValue.label = valueLabels[val];
}
return wrappedValue;
});
} else {
newState._valueList = filteredValueList;
}
@ -422,6 +467,7 @@ const Select = {
searchValue,
filterTreeNodeFn,
newState._valueEntities || prevState._valueEntities,
SelectNode,
);
}
@ -827,6 +873,7 @@ const Select = {
value,
filterTreeNodeFn,
valueEntities,
SelectNode,
),
});
}
@ -844,7 +891,13 @@ const Select = {
},
onChoiceAnimationLeave() {
this.forcePopupAlign();
raf(() => {
this.forcePopupAlign();
});
},
setPopupRef(popup) {
this.popup = popup;
},
/**
@ -1051,6 +1104,12 @@ const Select = {
on: {
treeExpanded: this.delayForcePopupAlign,
},
directives: [
{
name: 'ant-ref',
value: this.setPopupRef,
},
],
});
const Popup = isMultiple ? MultiplePopup : SinglePopup;

View File

@ -5,12 +5,25 @@ import {
convertTreeToEntities as vcConvertTreeToEntities,
conductCheck as rcConductCheck,
} from '../../vc-tree/src/util';
import SelectNode from './SelectNode';
import { hasClass } from '../../vc-util/Dom/class';
import { SHOW_CHILD, SHOW_PARENT } from './strategies';
import { getSlots, getPropsData, isEmptyElement } from '../../_util/props-util';
let warnDeprecatedLabel = false;
// =================== DOM =====================
export function findPopupContainer(node, prefixClass) {
let current = node;
while (current) {
if (hasClass(current, prefixClass)) {
return current;
}
current = current.parentNode;
}
return null;
}
// =================== MISC ====================
export function toTitle(title) {
if (typeof title === 'string') {
@ -190,7 +203,7 @@ export function cleanEntity({ node, pos, children }) {
* we have to convert `treeNodes > data > treeNodes` to keep the key.
* Such performance hungry!
*/
export function getFilterTree(h, treeNodes, searchValue, filterFunc, valueEntities) {
export function getFilterTree(h, treeNodes, searchValue, filterFunc, valueEntities, Component) {
if (!searchValue) {
return null;
}
@ -208,9 +221,9 @@ export function getFilterTree(h, treeNodes, searchValue, filterFunc, valueEntiti
.filter(n => n);
if (children.length || match) {
return (
<SelectNode {...node.data} key={valueEntities[getPropsData(node).value].key}>
<Component {...node.data} key={valueEntities[getPropsData(node).value].key}>
{children}
</SelectNode>
</Component>
);
}

View File

@ -1,4 +1,4 @@
// based on rc-tree 1.14.10
// based on rc-tree 2.1.3
'use strict';
module.exports = require('./src/');

View File

@ -108,8 +108,9 @@ const Tree = {
data() {
warning(this.$props.__propsSymbol__, 'must pass __propsSymbol__');
warning(this.$props.children, 'please children prop replace slots.default');
warning(this.$props.children, 'please use children prop replace slots.default');
this.needSyncKeys = {};
this.domTreeNodes = {};
const state = {
_posEntities: new Map(),
_keyEntities: new Map(),
@ -180,7 +181,6 @@ const Tree = {
// Calculate the entities data for quick match
const entitiesMap = convertTreeToEntities(treeNode);
newState._posEntities = entitiesMap.posEntities;
newState._keyEntities = entitiesMap.keyEntities;
}
@ -231,8 +231,7 @@ const Tree = {
if (!props.checkStrictly) {
const conductKeys = conductCheck(checkedKeys, true, keyEntities);
checkedKeys = conductKeys.checkedKeys;
halfCheckedKeys = conductKeys.halfCheckedKeys;
({ checkedKeys, halfCheckedKeys } = conductKeys);
}
newState._checkedKeys = checkedKeys;
@ -366,6 +365,7 @@ const Tree = {
dragNode: this.dragNode,
dragNodesKeys: _dragNodesKeys.slice(),
dropPosition: _dropPosition + Number(posArr[posArr.length - 1]),
dropToGap: false,
};
if (_dropPosition !== 0) {
@ -415,7 +415,7 @@ const Tree = {
selected: targetSelected,
node: treeNode,
selectedNodes,
nativeEvent: e,
nativeEvent: e.nativeEvent,
};
this.__emit('update:selectedKeys', selectedKeys);
this.__emit('select', selectedKeys, eventObj);
@ -499,16 +499,16 @@ const Tree = {
// Process load data
const promise = loadData(treeNode);
promise.then(() => {
const newLoadedKeys = arrAdd(this.$data._loadedKeys, eventKey);
const newLoadingKeys = arrDel(this.$data._loadingKeys, eventKey);
const { _loadedKeys: currentLoadedKeys, _loadingKeys: currentLoadingKeys } = this.$data;
const newLoadedKeys = arrAdd(currentLoadedKeys, eventKey);
const newLoadingKeys = arrDel(currentLoadingKeys, eventKey);
// onLoad should trigger before internal setState to avoid `loadData` trigger twice.
// https://github.com/ant-design/ant-design/issues/12464
const eventObj = {
this.__emit('load', newLoadedKeys, {
event: 'load',
node: treeNode,
};
this.__emit('load', newLoadedKeys, eventObj);
});
this.setUncontrolledState({
_loadedKeys: newLoadedKeys,
});
@ -598,6 +598,14 @@ const Tree = {
}
},
registerTreeNode(key, node) {
if (node) {
this.domTreeNodes[key] = node;
} else {
delete this.domTreeNodes[key];
}
},
isKeyChecked(key) {
const { _checkedKeys: checkedKeys = [] } = this.$data;
return checkedKeys.indexOf(key) !== -1;
@ -652,18 +660,15 @@ const Tree = {
render() {
const { _treeNode: treeNode } = this.$data;
const { prefixCls, focusable, showLine, tabIndex = 0 } = this.$props;
const domProps = {};
return (
<ul
{...domProps}
class={classNames(prefixCls, {
[`${prefixCls}-show-line`]: showLine,
})}
role="tree"
unselectable="on"
tabIndex={focusable ? tabIndex : null}
onKeydown={focusable ? this.onKeydown : () => {}}
>
{mapChildren(treeNode, (node, index) => this.renderTreeNode(node, index))}
</ul>

View File

@ -39,6 +39,7 @@ const TreeNode = {
// By user
isLeaf: PropTypes.bool,
checkable: PropTypes.bool,
selectable: PropTypes.bool,
disabled: PropTypes.bool,
disableCheckbox: PropTypes.bool,
@ -69,11 +70,23 @@ const TreeNode = {
// Isomorphic needn't load data in server side
mounted() {
const {
eventKey,
vcTree: { registerTreeNode },
} = this;
this.syncLoadData(this.$props);
registerTreeNode && registerTreeNode(eventKey, this);
},
updated() {
this.syncLoadData(this.$props);
},
beforeDestroy() {
const {
eventKey,
vcTree: { registerTreeNode },
} = this;
registerTreeNode && registerTreeNode(eventKey, null);
},
methods: {
onSelectorClick(e) {
@ -111,10 +124,10 @@ const TreeNode = {
const { disableCheckbox, checked } = this;
const {
vcTree: { checkable, onNodeCheck },
vcTree: { onNodeCheck },
} = this;
if (!checkable || disableCheckbox) return;
if (!this.isCheckable() || disableCheckbox) return;
e.preventDefault();
const targetChecked = !checked;
@ -275,18 +288,15 @@ const TreeNode = {
return !!(treeDisabled || disabled);
},
isSelectable() {
const { selectable } = this;
isCheckable() {
const { checkable } = this.$props;
const {
vcTree: { selectable: treeSelectable },
vcTree: { checkable: treeCheckable },
} = this;
// Ignore when selectable is undefined or null
if (typeof selectable === 'boolean') {
return selectable;
}
return treeSelectable;
// Return false if tree or treeNode is not checkable
if (!treeCheckable || checkable === false) return false;
return treeCheckable;
},
// Load data to avoid default expanded tree without data
@ -307,6 +317,20 @@ const TreeNode = {
}
},
isSelectable() {
const { selectable } = this;
const {
vcTree: { selectable: treeSelectable },
} = this;
// Ignore when selectable is undefined or null
if (typeof selectable === 'boolean') {
return selectable;
}
return treeSelectable;
},
// Switcher
renderSwitcher() {
const { expanded } = this;
@ -323,7 +347,7 @@ const TreeNode = {
class={classNames(`${prefixCls}-switcher`, `${prefixCls}-switcher-noop`)}
>
{typeof switcherIcon === 'function'
? cloneElement(switcherIcon({ ...this.$props, isLeaf: true }))
? switcherIcon({ ...this.$props, isLeaf: true })
: switcherIcon}
</span>
);
@ -336,7 +360,7 @@ const TreeNode = {
return (
<span key="switcher" onClick={this.onExpand} class={switcherCls}>
{typeof switcherIcon === 'function'
? cloneElement(switcherIcon({ ...this.$props, isLeaf: false }))
? switcherIcon({ ...this.$props, isLeaf: false })
: switcherIcon}
</span>
);
@ -346,9 +370,10 @@ const TreeNode = {
renderCheckbox() {
const { checked, halfChecked, disableCheckbox } = this;
const {
vcTree: { prefixCls, checkable },
vcTree: { prefixCls },
} = this;
const disabled = this.isDisabled();
const checkable = this.isCheckable();
if (!checkable) return null;

View File

@ -49,8 +49,8 @@ export function getNodeChildren(children = []) {
}
export function isCheckDisabled(node) {
const { disabled, disableCheckbox } = getOptionProps(node) || {};
return !!(disabled || disableCheckbox);
const { disabled, disableCheckbox, checkable } = getOptionProps(node) || {};
return !!(disabled || disableCheckbox) || checkable === false;
}
export function traverseTreeNodes(treeNodes, callback) {
@ -116,7 +116,8 @@ export function calcDropPosition(event, treeNode) {
if (clientY <= top + des) {
return -1;
} else if (clientY >= bottom - des) {
}
if (clientY >= bottom - des) {
return 1;
}
return 0;
@ -162,13 +163,13 @@ const internalProcessProps = (props = {}) => {
key: props.key,
};
};
export function convertDataToTree(h, treeData, processer) {
export function convertDataToTree(h, treeData, processor) {
if (!treeData) return [];
const { processProps = internalProcessProps } = processer || {};
const { processProps = internalProcessProps } = processor || {};
const list = Array.isArray(treeData) ? treeData : [treeData];
return list.map(({ children, ...props }) => {
const childrenNodes = convertDataToTree(h, children, processer);
const childrenNodes = convertDataToTree(h, children, processor);
return <TreeNode {...processProps(props)}>{childrenNodes}</TreeNode>;
});
}
@ -398,8 +399,8 @@ export function conductExpandParent(keyList, keyEntities) {
expandedKeys.set(key, true);
const { parent, node } = entity;
if (isCheckDisabled(node)) return;
const props = getOptionProps(node);
if (props && props.disabled) return;
if (parent) {
conductUp(parent.key);

View File

@ -1,61 +0,0 @@
/* @flow */
/**
* Add class with compatibility for SVG since classList is not supported on
* SVG elements in IE
*/
export function addClass(el, cls) {
/* istanbul ignore if */
if (!cls || !(cls = cls.trim())) {
return;
}
/* istanbul ignore else */
if (el.classList) {
if (cls.indexOf(' ') > -1) {
cls.split(/\s+/).forEach(c => el.classList.add(c));
} else {
el.classList.add(cls);
}
} else {
const cur = ` ${el.getAttribute('class') || ''} `;
if (cur.indexOf(' ' + cls + ' ') < 0) {
el.setAttribute('class', (cur + cls).trim());
}
}
}
/**
* Remove class with compatibility for SVG since classList is not supported on
* SVG elements in IE
*/
export function removeClass(el, cls) {
/* istanbul ignore if */
if (!cls || !(cls = cls.trim())) {
return;
}
/* istanbul ignore else */
if (el.classList) {
if (cls.indexOf(' ') > -1) {
cls.split(/\s+/).forEach(c => el.classList.remove(c));
} else {
el.classList.remove(cls);
}
if (!el.classList.length) {
el.removeAttribute('class');
}
} else {
let cur = ` ${el.getAttribute('class') || ''} `;
const tar = ' ' + cls + ' ';
while (cur.indexOf(tar) >= 0) {
cur = cur.replace(tar, ' ');
}
cur = cur.trim();
if (cur) {
el.setAttribute('class', cur);
} else {
el.removeAttribute('class');
}
}
}

View File

@ -0,0 +1,28 @@
export function hasClass(node, className) {
if (node.classList) {
return node.classList.contains(className);
}
const originClass = node.className;
return ` ${originClass} `.indexOf(` ${className} `) > -1;
}
export function addClass(node, className) {
if (node.classList) {
node.classList.add(className);
} else {
if (!hasClass(node, className)) {
node.className = `${node.className} ${className}`;
}
}
}
export function removeClass(node, className) {
if (node.classList) {
node.classList.remove(className);
} else {
if (hasClass(node, className)) {
const originClass = node.className;
node.className = ` ${originClass} `.replace(` ${className} `, ' ');
}
}
}

View File

@ -3,7 +3,7 @@ export default function mountTest(Component) {
describe(`mount and unmount`, () => {
// https://github.com/ant-design/ant-design/pull/18441
it(`component could be updated and unmounted without errors`, () => {
const wrapper = mount(Component);
const wrapper = mount(Component, { sync: false });
expect(() => {
wrapper.vm.$forceUpdate();
wrapper.destroy();

View File

@ -1,6 +1,6 @@
// Project: https://github.com/vueComponent/ant-design-vue
// Definitions by: akki-jat <https://github.com/akki-jat>
// Definitions: https://github.com/vueComponent/ant-design-vue/types
// Project: https://github.com/vueComponent/ant-design-vue Definitions by:
// akki-jat <https://github.com/akki-jat> Definitions:
// https://github.com/vueComponent/ant-design-vue/types
import { AntdComponent } from './component';
import { TreeNode } from './tree-node';
@ -15,6 +15,8 @@ export interface TreeData {
selectable?: boolean;
}
export type TreeNodeValue = string | number | string[] | number[];
export declare class TreeSelect extends AntdComponent {
static TreeNode: typeof TreeNode;
@ -29,11 +31,7 @@ export declare class TreeSelect extends AntdComponent {
*/
allowClear: boolean;
/**
* To set the initial selected treeNode(s).
* @type string | string[]
*/
defaultValue: string | string[];
defaultValue: TreeNodeValue;
/**
* Disabled or not
@ -171,7 +169,13 @@ export declare class TreeSelect extends AntdComponent {
* @default false
* @type boolean | object[]
*/
treeDataSimpleMode: boolean | Array<{ id: string; pId: string; rootPId: any }>;
treeDataSimpleMode:
| boolean
| Array<{
id: string;
pId: string;
rootPId: any;
}>;
/**
* Whether to expand all treeNodes by default
@ -206,11 +210,7 @@ export declare class TreeSelect extends AntdComponent {
*/
treeNodeLabelProp: string;
/**
* To set the current selected treeNode(s).
* @type string | string[]
*/
value: string | string[];
value: TreeNodeValue;
/**
* The custom suffix icon
@ -218,6 +218,10 @@ export declare class TreeSelect extends AntdComponent {
*/
suffixIcon: any;
removeIcon?: any;
clearIcon?: any;
/**
* remove focus
*/