From 9f7f9cbe6ba3f8ff9119ee1fff52c647734b6652 Mon Sep 17 00:00:00 2001
From: Tao Lei <108162283+zizairufengLT@users.noreply.github.com>
Date: Mon, 3 Jun 2024 10:47:32 +0800
Subject: [PATCH] refactor(TreeSelect): convert to TypeScript, improve docs and
tests, close #4620 (#4837)
---
.../tree-select/__docs__/adaptor/index.jsx | 112 ---
.../tree-select/__docs__/adaptor/index.tsx | 144 ++++
.../__docs__/demo/accessibility/index.tsx | 11 +-
.../tree-select/__docs__/demo/basic/index.tsx | 11 +-
.../tree-select/__docs__/demo/check/index.tsx | 11 +-
.../__docs__/demo/control/index.tsx | 17 +-
.../tree-select/__docs__/demo/data/index.tsx | 11 +-
.../__docs__/demo/inline/index.tsx | 17 +-
.../demo/non-existent-value/index.tsx | 11 +-
.../__docs__/demo/pro-search/index.tsx | 22 +-
.../__docs__/demo/search/index.tsx | 17 +-
.../__docs__/demo/select/index.tsx | 20 +-
.../__docs__/demo/virtual-tree/index.tsx | 24 +-
.../tree-select/__docs__/index.en-us.md | 88 +-
components/tree-select/__docs__/index.md | 93 ++-
.../__docs__/theme/{index.jsx => index.tsx} | 121 ++-
.../{index-spec.js => index-spec.tsx} | 780 +++++++++---------
components/tree-select/index.d.ts | 252 ------
.../tree-select/{index.jsx => index.tsx} | 13 +-
.../mobile/{index.jsx => index.tsx} | 1 +
components/tree-select/{style.js => style.ts} | 0
.../{tree-select.jsx => tree-select.tsx} | 400 ++++-----
components/tree-select/types.ts | 375 +++++++++
components/tree/__docs__/index.en-us.md | 84 +-
components/tree/__docs__/index.md | 84 +-
components/tree/types.ts | 8 +
components/tree/view/util.ts | 1 +
27 files changed, 1397 insertions(+), 1331 deletions(-)
delete mode 100644 components/tree-select/__docs__/adaptor/index.jsx
create mode 100644 components/tree-select/__docs__/adaptor/index.tsx
rename components/tree-select/__docs__/theme/{index.jsx => index.tsx} (53%)
rename components/tree-select/__tests__/{index-spec.js => index-spec.tsx} (52%)
delete mode 100644 components/tree-select/index.d.ts
rename components/tree-select/{index.jsx => index.tsx} (52%)
rename components/tree-select/mobile/{index.jsx => index.tsx} (77%)
rename components/tree-select/{style.js => style.ts} (100%)
rename components/tree-select/{tree-select.jsx => tree-select.tsx} (72%)
create mode 100644 components/tree-select/types.ts
diff --git a/components/tree-select/__docs__/adaptor/index.jsx b/components/tree-select/__docs__/adaptor/index.jsx
deleted file mode 100644
index c0db1fcfc1..0000000000
--- a/components/tree-select/__docs__/adaptor/index.jsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import React from 'react';
-import { Types, parseData, NodeType } from '@alifd/adaptor-helper';
-import { TreeSelect } from '@alifd/next';
-
-const createDataSouce = (list, keys = { selected: [], expanded: [] }, level = 0, prefix='') => {
- const array = [];
- let index = 0;
-
- list.forEach((item) => {
- const key = `${prefix || level }-${index++}`;
-
- if (item.children && item.children.length > 0) {
- item.children = createDataSouce(item.children, keys, level + 1, key);
- }
- array.push({
- label: item.value,
- value: key,
- disabled: item.state === 'disabled',
- key,
- children: item.children
- });
-
- if (item.state === 'active') {
- if (item.children && item.children.length > 0) {
- keys.expanded.push(key);
- } else {
- keys.selected.push(key);
- }
- }
-
- return;
- });
-
- return array;
-};
-
-export default {
- name: 'TreeSelect',
- editor: () => ({
- props: [{
- name: 'state',
- label: 'Status',
- type: Types.enum,
- options: ['normal', 'expanded', 'disabled'],
- default: 'normal'
- }, {
- name: 'size',
- type: Types.enum,
- options: ['large', 'medium', 'small'],
- default: 'medium'
- }, {
- name: 'width',
- type: Types.number,
- default: 300,
- }, {
- name: 'border',
- type: Types.bool,
- default: true
- }, {
- name: 'checkbox',
- type: Types.bool,
- default: false
- }, {
- name: 'label',
- type: Types.string,
- default: ''
- }, {
- name: 'placeholder',
- type: Types.string,
- default: 'Please Select'
- }],
- data: {
- active: true,
- disabled: true,
- default: '*Trunk\n\t-Branch\n\t\t*Branch\n\t\t\tLeaf\n\t\tLeaf\n\t*Branch\n\t\tLeaf\n\t\tLeaf'
- }
- }),
- adaptor: ({ state, size, width, border, checkbox, label, placeholder, data, style, ...others }) => {
- const list = parseData(data).filter(({ type }) => type === NodeType.node);
- const keys = { selected: [], expanded: [] };
- const dataSource = createDataSouce(list, keys);
-
- const props = {
- ...others,
- style: { width, ...style },
- size,
- dataSource,
- key: new Date().getTime(),
- multiple: checkbox,
- treeCheckable: checkbox,
- treeDefaultExpandAll: true,
- hasBorder: border,
- disabled: state === 'disabled',
- visible: state === 'expanded',
- label,
- placeholder,
- popupContainer: node => node,
- popupProps: { needAdjust: false },
- value: checkbox ? keys.selected : keys.selected[0],
- };
-
- return (
-
- );
- },
- demoOptions: (demo) => {
- if (demo.node.props.state === 'expanded') {
- demo.height = 300;
- }
- return demo;
- }
-};
diff --git a/components/tree-select/__docs__/adaptor/index.tsx b/components/tree-select/__docs__/adaptor/index.tsx
new file mode 100644
index 0000000000..55e3ff1156
--- /dev/null
+++ b/components/tree-select/__docs__/adaptor/index.tsx
@@ -0,0 +1,144 @@
+import React from 'react';
+import { Types, parseData, NodeType } from '@alifd/adaptor-helper';
+import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
+
+const createDataSouce = (
+ list: any[],
+ keys: { selected: string[]; expanded: string[] } = { selected: [], expanded: [] },
+ level = 0,
+ prefix = ''
+) => {
+ const array: any[] = [];
+ let index = 0;
+
+ list.forEach(item => {
+ const key = `${prefix || level}-${index++}`;
+
+ if (item.children && item.children.length > 0) {
+ item.children = createDataSouce(item.children, keys, level + 1, key);
+ }
+ array.push({
+ label: item.value,
+ value: key,
+ disabled: item.state === 'disabled',
+ key,
+ children: item.children,
+ });
+
+ if (item.state === 'active') {
+ if (item.children && item.children.length > 0) {
+ keys.expanded.push(key);
+ } else {
+ keys.selected.push(key);
+ }
+ }
+
+ return;
+ });
+
+ return array;
+};
+
+export default {
+ name: 'TreeSelect',
+ editor: () => ({
+ props: [
+ {
+ name: 'state',
+ label: 'Status',
+ type: Types.enum,
+ options: ['normal', 'expanded', 'disabled'],
+ default: 'normal',
+ },
+ {
+ name: 'size',
+ type: Types.enum,
+ options: ['large', 'medium', 'small'],
+ default: 'medium',
+ },
+ {
+ name: 'width',
+ type: Types.number,
+ default: 300,
+ },
+ {
+ name: 'border',
+ type: Types.bool,
+ default: true,
+ },
+ {
+ name: 'checkbox',
+ type: Types.bool,
+ default: false,
+ },
+ {
+ name: 'label',
+ type: Types.string,
+ default: '',
+ },
+ {
+ name: 'placeholder',
+ type: Types.string,
+ default: 'Please Select',
+ },
+ ],
+ data: {
+ active: true,
+ disabled: true,
+ default:
+ '*Trunk\n\t-Branch\n\t\t*Branch\n\t\t\tLeaf\n\t\tLeaf\n\t*Branch\n\t\tLeaf\n\t\tLeaf',
+ },
+ }),
+ adaptor: ({
+ state,
+ size,
+ width,
+ border,
+ checkbox,
+ label,
+ placeholder,
+ data,
+ style,
+ ...others
+ }: TreeSelectProps & {
+ state: 'normal' | 'expanded' | 'disabled';
+ width: number;
+ border: boolean;
+ shape: 'normal' | 'line';
+ select: 'node' | 'label';
+ checkbox: boolean;
+ data: string;
+ }) => {
+ const list = parseData(data).filter(({ type }: any) => type === NodeType.node);
+ const keys = { selected: [], expanded: [] };
+ const dataSource = createDataSouce(list, keys);
+
+ const props = {
+ ...others,
+ style: { width, ...style },
+ size,
+ dataSource,
+ key: new Date().getTime(),
+ multiple: checkbox,
+ treeCheckable: checkbox,
+ treeDefaultExpandAll: true,
+ hasBorder: border,
+ disabled: state === 'disabled',
+ visible: state === 'expanded',
+ label,
+ placeholder,
+ popupContainer: (node: HTMLElement) => node,
+ popupProps: { needAdjust: false },
+ value: checkbox ? keys.selected : keys.selected[0],
+ };
+
+ return ;
+ },
+ demoOptions: (demo: any) => {
+ if (demo.node.props.state === 'expanded') {
+ demo.height = 300;
+ }
+ return demo;
+ },
+};
diff --git a/components/tree-select/__docs__/demo/accessibility/index.tsx b/components/tree-select/__docs__/demo/accessibility/index.tsx
index be017d8919..20925b418e 100644
--- a/components/tree-select/__docs__/demo/accessibility/index.tsx
+++ b/components/tree-select/__docs__/demo/accessibility/index.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
const data = [
{
@@ -37,15 +38,9 @@ const data = [
];
class Demo extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(value, data) {
+ handleChange: TreeSelectProps['onChange'] = (value, data) => {
console.log(value, data);
- }
+ };
render() {
return (
diff --git a/components/tree-select/__docs__/demo/basic/index.tsx b/components/tree-select/__docs__/demo/basic/index.tsx
index e7a4100d76..077c9fcee0 100644
--- a/components/tree-select/__docs__/demo/basic/index.tsx
+++ b/components/tree-select/__docs__/demo/basic/index.tsx
@@ -2,19 +2,14 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
const TreeNode = TreeSelect.Node;
class Demo extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(value, data) {
+ handleChange: TreeSelectProps['onChange'] = (value, data) => {
console.log(value, data);
- }
+ };
render() {
return (
diff --git a/components/tree-select/__docs__/demo/check/index.tsx b/components/tree-select/__docs__/demo/check/index.tsx
index f144a48fb2..a92961881b 100644
--- a/components/tree-select/__docs__/demo/check/index.tsx
+++ b/components/tree-select/__docs__/demo/check/index.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
const treeData = [
{
@@ -36,15 +37,9 @@ const treeData = [
];
class Demo extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(value, data) {
+ handleChange: TreeSelectProps['onChange'] = (value, data) => {
console.log(value, data);
- }
+ };
render() {
return (
diff --git a/components/tree-select/__docs__/demo/control/index.tsx b/components/tree-select/__docs__/demo/control/index.tsx
index 8ed49df2e2..451865bbd6 100644
--- a/components/tree-select/__docs__/demo/control/index.tsx
+++ b/components/tree-select/__docs__/demo/control/index.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
const treeData = [
{
@@ -36,22 +37,16 @@ const treeData = [
];
class Demo extends React.Component {
- constructor(props) {
- super(props);
+ state = {
+ value: ['4', '6'],
+ };
- this.state = {
- value: ['4', '6'],
- };
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(value, data) {
+ handleChange: TreeSelectProps['onChange'] = (value, data) => {
console.log(value, data);
this.setState({
value,
});
- }
+ };
render() {
return (
diff --git a/components/tree-select/__docs__/demo/data/index.tsx b/components/tree-select/__docs__/demo/data/index.tsx
index 4a7b1e6da6..7d9aa2e596 100644
--- a/components/tree-select/__docs__/demo/data/index.tsx
+++ b/components/tree-select/__docs__/demo/data/index.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
const treeData = [
{
@@ -36,15 +37,9 @@ const treeData = [
},
];
class Demo extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(value, data) {
+ handleChange: TreeSelectProps['onChange'] = (value, data) => {
console.log(value, data);
- }
+ };
render() {
return (
diff --git a/components/tree-select/__docs__/demo/inline/index.tsx b/components/tree-select/__docs__/demo/inline/index.tsx
index 8610c54cd2..238955ffb9 100644
--- a/components/tree-select/__docs__/demo/inline/index.tsx
+++ b/components/tree-select/__docs__/demo/inline/index.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
const treeData = [
{
@@ -44,22 +45,16 @@ const treeData = [
];
class Demo extends React.Component {
- constructor(props) {
- super(props);
+ state = {
+ value: ['4', '6'],
+ };
- this.state = {
- value: ['4', '6'],
- };
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(value, data) {
+ handleChange: TreeSelectProps['onChange'] = (value, data) => {
console.log(value, data);
this.setState({
value,
});
- }
+ };
render() {
return (
diff --git a/components/tree-select/__docs__/demo/non-existent-value/index.tsx b/components/tree-select/__docs__/demo/non-existent-value/index.tsx
index dbe8094d86..bf5cbaf80e 100644
--- a/components/tree-select/__docs__/demo/non-existent-value/index.tsx
+++ b/components/tree-select/__docs__/demo/non-existent-value/index.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
const treeData = [
{
@@ -37,15 +38,9 @@ const treeData = [
];
class Demo extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(value, data) {
+ handleChange: TreeSelectProps['onChange'] = (value, data) => {
console.log(value, data);
- }
+ };
render() {
return (
diff --git a/components/tree-select/__docs__/demo/pro-search/index.tsx b/components/tree-select/__docs__/demo/pro-search/index.tsx
index 22a01ad33c..399be1783a 100644
--- a/components/tree-select/__docs__/demo/pro-search/index.tsx
+++ b/components/tree-select/__docs__/demo/pro-search/index.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
const defaultTreeData = [
{
@@ -16,22 +17,17 @@ const defaultTreeData = [
];
class Demo extends React.Component {
- constructor(props) {
- super(props);
+ timeId: number;
+ state = {
+ value: ['浙江'],
+ treeData: defaultTreeData,
+ };
- this.state = {
- value: ['浙江'],
- treeData: defaultTreeData,
- };
-
- this.handleSearch = this.handleSearch.bind(this);
- }
-
- handleSearch(searchVal, data) {
+ handleSearch: TreeSelectProps['onSearch'] = searchVal => {
clearTimeout(this.timeId);
if (searchVal) {
- this.timeId = setTimeout(() => {
+ this.timeId = window.setTimeout(() => {
this.setState({
treeData: [
{
@@ -46,7 +42,7 @@ class Demo extends React.Component {
treeData: defaultTreeData,
});
}
- }
+ };
render() {
return (
diff --git a/components/tree-select/__docs__/demo/search/index.tsx b/components/tree-select/__docs__/demo/search/index.tsx
index cb0d182c97..c010b02a8f 100644
--- a/components/tree-select/__docs__/demo/search/index.tsx
+++ b/components/tree-select/__docs__/demo/search/index.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
const treeData = [
{
@@ -38,19 +39,13 @@ const treeData = [
];
class Demo extends React.Component {
- constructor(props) {
- super(props);
+ state = {
+ value: ['4', '6'],
+ };
- this.state = {
- value: ['4', '6'],
- };
-
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(value, data) {
+ handleChange: TreeSelectProps['onChange'] = (value, data) => {
console.log(value, data);
- }
+ };
render() {
return (
diff --git a/components/tree-select/__docs__/demo/select/index.tsx b/components/tree-select/__docs__/demo/select/index.tsx
index f49b419fa0..e26cae2a77 100644
--- a/components/tree-select/__docs__/demo/select/index.tsx
+++ b/components/tree-select/__docs__/demo/select/index.tsx
@@ -36,22 +36,11 @@ const dataSource = [
},
];
class Demo extends React.Component {
- constructor(props) {
- super(props);
+ state = {
+ multiple: false,
+ };
- this.state = {
- multiple: false,
- };
-
- this.handleCheck = this.handleCheck.bind(this);
- this.handleChange = this.handleChange.bind(this);
- }
-
- handleChange(value, data) {
- console.log(value, data);
- }
-
- handleCheck(v) {
+ handleCheck(v: boolean) {
this.setState({
multiple: v,
});
@@ -70,7 +59,6 @@ class Demo extends React.Component {
treeDefaultExpandAll
hasClear
multiple={multiple}
- onSelect={this.handleSelect}
dataSource={dataSource}
style={{ width: 200 }}
/>
diff --git a/components/tree-select/__docs__/demo/virtual-tree/index.tsx b/components/tree-select/__docs__/demo/virtual-tree/index.tsx
index b88a09635e..b840a093a3 100644
--- a/components/tree-select/__docs__/demo/virtual-tree/index.tsx
+++ b/components/tree-select/__docs__/demo/virtual-tree/index.tsx
@@ -1,13 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { TreeSelect } from '@alifd/next';
+import type { DataNode } from '@alifd/next/es/tree/types';
+import type { TreeSelectProps } from '@alifd/next/lib/tree-select';
function createDataSource(level = 3, count = 5) {
const dataSource = [];
let num = 0;
- const drill = (children, _level, _count) => {
- children.forEach((child, i) => {
+ const drill = (children: DataNode[], _level: number, _count: number) => {
+ children.forEach(child => {
child.children = new Array(_count).fill(null).map((item, k) => {
const key = `${child.key}-${k}`;
num++;
@@ -32,17 +34,9 @@ function createDataSource(level = 3, count = 5) {
}
class Demo extends React.Component {
- constructor() {
- super();
-
- this.state = {
- dataSource: [],
- };
- }
-
- onChange(keys, info) {
- console.log('onSelect', keys, info);
- }
+ state = {
+ dataSource: [],
+ };
componentDidMount() {
this.setState({
@@ -50,6 +44,10 @@ class Demo extends React.Component {
});
}
+ onChange: TreeSelectProps['onChange'] = (keys, info) => {
+ console.log('onSelect', keys, info);
+ };
+
render() {
const dataSource = this.state.dataSource;
diff --git a/components/tree-select/__docs__/index.en-us.md b/components/tree-select/__docs__/index.en-us.md
index dd78ea5820..242d3ed53a 100644
--- a/components/tree-select/__docs__/index.en-us.md
+++ b/components/tree-select/__docs__/index.en-us.md
@@ -17,48 +17,52 @@ Like Select, TreeSelect can be used when the selected data structure is a tree s
### TreeSelect
-| Param | Descripiton | Type | Default Value |
-| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ---------------------------------- | ---- |
-| children | tree nodes | ReactNode | - |
-| size | size of selector
**options**:
'small', 'medium', 'large' | Enum | 'medium' |
-| placeholder | placeholder of selector | String | - |
-| disabled | whether selector is disabled | Boolean | false |
-| hasArrow | whether has arrow icon | Boolean | true |
-| hasBorder | whether selector has border | Boolean | true |
-| label | custom inline label | ReactNode | - |
-| readOnly | whether selector is read only, it can be expanded but cannot be selected under read only mode | Boolean | - |
-| autoWidth | whether the dropdown is aligned with the selector | Boolean | true |
-| dataSource | data source, this property has a higher priority than children | Array<Object> | - |
-| value | (under control) current value | String/Array<String> | - |
-| defaultValue | (under uncontrol) default value | String/Array<String> | null |
-| onChange | callback triggered when value change
**signatures**:
Function(value: String/Array, data: Object/Array) => void
**params**:
_value_: {String/Array} selected value, a single value is returned when single select, and an array is returned when multiple select
_data_: {Object/Array} selected data, including value, label, pos, and key properties, returns a single value when single select, returns an array when multiple select, parent and child nodes are selected at the same time, only the parent node is returned. | Function | () => {} |
-| showSearch | whether to show the search box | Boolean | false |
-| onSearch | callback triggered when search
**signatures**:
Function(keyword: String) => void
**params**:
_keyword_: {String} input keyword | Function | () => {} |
-| notFoundContent | content without data | ReactNode | 'Not Found' |
-| multiple | whether it support multiple selection | Boolean | false |
-| treeCheckable | whether the tree in the dropdown supports the checkbox of the node | Boolean | false |
-| treeCheckStrictly | whether the checkbox of the node is controlled strictly (selection of parent and child nodes are no longer related) | Boolean | false |
-| treeCheckedStrategy | defining the way to backfill when checked node
**options**:
'all' (return all checked nodes)
'parent' (only parent nodes are returned when parent and child nodes are checked)
'child' (only child nodes are returned when parent and child nodes are checked) | Enum | 'parent' |
-| treeDefaultExpandAll | whether to expand all nodes by default | Boolean | false |
-| treeDefaultExpandedKeys | keys of default expanded nodes | Array<String> | \[] |
-| treeLoadData | asynchronous data loading function, Please refer to [Tree's asynchronous loading data Demo](https://fusion.design/pc/component/basic/tree#%E5%BC%82%E6%AD%A5%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE)
**signatures**:
Function(node: ReactElement) => void
**params**:
_node_: {ReactElement} clicked node | Function | - |
-| treeProps | properties of Tree | Object | {} |
-| defaultVisible | whether the dropdown box is displayed in default | Boolean | false |
-| visible | whether the dropdown box is displayed currently | Boolean | - |
-| onVisibleChange | callback triggered when open or close the dropdown
**signatures**:
Function(visible: Boolean, type: String) => void
**params**:
_visible_: {Boolean} whether is visible
_type_: {String} trigger type | Function | () => {} |
-| popupStyle | style of dropdown | Object | - |
-| popupClassName | class name of dropdown | String | - |
-| popupContainer | container of dropdown | String/Function | - |
-| popupProps | properties of Popup | Object | - |
-| followTrigger | follow Trigger or not | Boolean | - |
-| useVirtual | whether use virtual scroll | Boolean | false |
-| tagInline | if display in one line | Boolean | false | 1.25 |
-| maxTagPlaceholder | return custom content when hide extra tags, valid when tagInline is true
**signature**:
Function(selectedValues: Array, totalValues: Array) => reactNode
**params**:
_selectedValues_: {Array} current selected values
_totalValues_: {Array} all avaliable values
**returns**:
{reactNode} null
| Function | - | 1.25 |
-| preserveNonExistentValue | if reserve value when value/defaultValue not exist in dataSource | Boolean | false | 1.25 |
-| autoClearSearch | auto clear search value when choose item | Boolean | true | 1.26 |
-| clickToCheck | whether clicking on the text can be checked. When it is true, selectable defaults to false. | Boolean | false |
-| valueRender | Methods for rendering Select to display content
** Parameters**:
_item_: {Object} Render node's item
** Parameters**:
_itemPaths_: {Object[]} item full paths
**return value **:
{ReactNode} show content
| Function | item => item.label \|\| item.value |
-| useDetailValue | The first parameter of onChange returns the object in dataSource | Boolean | - |
+| Param | Description | Type | Default Value | Required | Supported Version |
+| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------- | ----------------- |
+| children | Tree node | React.ReactNode | - | | - |
+| size | Select size | 'small' \| 'medium' \| 'large' | 'medium' | | - |
+| placeholder | Select placeholder | string | - | | - |
+| disabled | Whether to be disabled | boolean | false | | - |
+| hasArrow | Whether to show the arrow | boolean | true | | - |
+| hasBorder | Whether to show the border | boolean | true | | - |
+| hasClear | Whether to show the clear button | boolean | true | | - |
+| label | Custom inline label | React.ReactNode | - | | - |
+| readOnly | Whether to be read-only (read | boolean | - | | - |
+| autoWidth | Whether the drop | boolean | true | | - |
+| dataSource | Data source (higher priority than children) | DataSourceItem[] | - | | - |
+| value | Current value (Controlled) | DataSourceItem[] \| DataSourceItem | - | | - |
+| defaultValue | Default value (Uncontrolled) | SelectProps['defaultValue'] | null | | - |
+| preserveNonExistentValue | Whether to display when value/defaultValue does not exist in dataSource | boolean | false | | 1.25 |
+| onChange | Callback when the selected value changes | (
value: DataSourceItem[] \| DataSourceItem,
data: ObjectItem[] \| ObjectItem \| null
) => void | () =\> \{\} | | - |
+| tagInline | Whether to display on one line (only effective when multiple and treeCheckable are true) | boolean | false | | 1.25 |
+| maxTagPlaceholder | Content to display when hiding excess tags (effective when tagInline is true)
**signature**:
**params**:
_selectedValues_: Selected element
_totalValues_: Total pending element, treeCheckedStrategy = 'parent' is undefined
**return**:
ReactNode or HTMLElement | (
selectedValues: ObjectItem[],
totalValues?: ObjectItem[]
) => React.ReactNode \| HTMLElement | - | | 1.25 |
+| autoClearSearch | Whether to automatically clear searchValue | boolean | true | | 1.26 |
+| showSearch | Whether to show the search box | boolean | false | | - |
+| onSearch | Callback when input in search box changes | (keyword: string) => void | () =\> \{\} | | - |
+| notFoundContent | Content to display when there is no data | React.ReactNode | 'Not Found' | | - |
+| multiple | Whether to support multiple selection | boolean | false | | - |
+| treeCheckable | Whether the check box of the tree in the drop | boolean | false | | - |
+| treeCheckStrictly | Whether the check box of the tree in the drop-down box is completely controlled (the parent | boolean | false | | - |
+| treeCheckedStrategy | Definition of how to fill in when selected | 'all' \| 'parent' \| 'child' | 'parent' | | - |
+| treeDefaultExpandAll | Whether the tree in the drop | boolean | false | | - |
+| treeDefaultExpandedKeys | The array of keys of the nodes expanded by default in the tree in the drop | Array\ | [] | | - |
+| treeLoadData | The function of asynchronous loading data in the tree in the drop | TreeProps['loadData'] | - | | - |
+| treeProps | Pass | TreeProps | \{\} | | - |
+| defaultVisible | Initial display state of the drop | boolean | false | | - |
+| visible | Current display state of the drop | boolean | - | | - |
+| onVisibleChange | Callback when the drop | (visible: boolean, type: string) => void | () =\> \{\} | | - |
+| popupStyle | Custom style object for the drop | React.CSSProperties | - | | - |
+| popupClassName | Custom class name for the drop | string | - | | - |
+| popupContainer | Mounting container node for the drop | string \| HTMLElement \| ((target: HTMLElement) => HTMLElement) | - | | - |
+| popupProps | Pass | PopupProps | - | | - |
+| followTrigger | Whether to follow scrolling | boolean | - | | - |
+| isPreview | Whether it is in preview mode | boolean | - | | - |
+| renderPreview | Content rendered in preview mode | (data: ObjectItem[], props: TreeSelectProps) => React.ReactNode | - | | - |
+| useVirtual | Whether to open virtual scrolling | boolean | false | | - |
+| filterLocal | Whether to close local search | boolean | true | | - |
+| immutable | Whether it is immutable data | boolean | - | | 1.23 |
+| clickToCheck | Whether clicking on the text can be selected | boolean | false | | - |
+| valueRender | Method for rendering Select area display content
**signature**:
**params**:
_item_: Extra item
_itemPaths_: Extra item path
**return**:
Display content | (item: TreeSelectState['\_k2n'][Key], itemPaths: ObjectItem[]) => React.ReactNode | (item) =\> item.label \|\| item.value | | - |
diff --git a/components/tree-select/__docs__/index.md b/components/tree-select/__docs__/index.md
index 62e6a30069..4485d81d0a 100644
--- a/components/tree-select/__docs__/index.md
+++ b/components/tree-select/__docs__/index.md
@@ -17,53 +17,52 @@
### TreeSelect
-| 参数 | 说明 | 类型 | 默认值 | 版本支持 |
-| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ------------------- | -------- | ----------- | --- |
-| children | 树节点 | ReactNode | - | |
-| size | 选择框大小
**可选值**:
'small', 'medium', 'large' | Enum | 'medium' | |
-| placeholder | 选择框占位符 | String | - | |
-| disabled | 是否禁用 | Boolean | false | |
-| hasArrow | 是否有下拉箭头 | Boolean | true | |
-| hasBorder | 是否有边框 | Boolean | true | |
-| hasClear | 是否有清空按钮 | Boolean | false | |
-| label | 自定义内联 label | ReactNode | - | |
-| readOnly | 是否只读,只读模式下可以展开弹层但不能选择 | Boolean | - | |
-| autoWidth | 下拉框是否与选择器对齐 | Boolean | true | |
-| dataSource | 数据源,该属性优先级高于 children | Array<Object> | - | |
-| preserveNonExistentValue | value/defaultValue 在 dataSource 中不存在时,是否展示 | Boolean | false | 1.25 |
-| value | (受控)当前值 | String/Object/Array<any> | - | |
-| defaultValue | (非受控)默认值 | String/Object/Array<any> | null | |
-| onChange | 选中值改变时触发的回调函数
**签名**:
Function(value: String/Array, data: Object/Array) => void
**参数**:
_value_: {String/Array} 选中的值,单选时返回单个值,多选时返回数组
_data_: {Object/Array} 选中的数据,包括 value, label, pos, key属性,单选时返回单个值,多选时返回数组,父子节点选中关联时,同时选中,只返回父节点 | Function | () => {} | |
-| tagInline | 是否一行显示,仅在 multiple 和 treeCheckable 为 true 时生效 | Boolean | false | 1.25 |
-| maxTagPlaceholder | 隐藏多余 tag 时显示的内容,在 tagInline 生效时起作用
**签名**:
Function(selectedValues: Array, totalValues: Array) => reactNode
**参数**:
_selectedValues_: {Array} 当前已选中的元素
_totalValues_: {Array} 总待选元素
**返回值**:
{reactNode} null
| Function | - | 1.25 |
-| autoClearSearch | 选择时是否自动清空 searchValue | Boolean | true | 1.26 |
-| showSearch | 是否显示搜索框 | Boolean | false | |
-| filterLocal | 是否使用本地过滤,在数据源为远程的时候需要关闭此项 | Boolean | true | |
-| onSearch | 在搜索框中输入时触发的回调函数
**签名**:
Function(keyword: String) => void
**参数**:
_keyword_: {String} 输入的关键字 | Function | () => {} | |
-| notFoundContent | 无数据时显示内容 | ReactNode | 'Not Found' | |
-| multiple | 是否支持多选 | Boolean | false | |
-| treeCheckable | 下拉框中的树是否支持勾选节点的复选框 | Boolean | false | |
-| treeCheckStrictly | 下拉框中的树勾选节点复选框是否完全受控(父子节点选中状态不再关联) | Boolean | false | |
-| treeCheckedStrategy | 定义选中时回填的方式
**可选值**:
'all'(返回所有选中的节点)
'parent'(父子节点都选中时只返回父节点)
'child'(父子节点都选中时只返回子节点) | Enum | 'parent' | |
-| treeDefaultExpandAll | 下拉框中的树是否默认展开所有节点 | Boolean | false | |
-| treeDefaultExpandedKeys | 下拉框中的树默认展开节点key的数组 | Array<String> | \[] | |
-| treeLoadData | 下拉框中的树异步加载数据的函数,使用请参考[Tree的异步加载数据Demo](https://fusion.design/pc/component/tree?themeid=2#dynamic-container)
**签名**:
Function(node: ReactElement) => void
**参数**:
_node_: {ReactElement} 被点击展开的节点 | Function | - | |
-| treeProps | 透传到 Tree 的属性对象 | Object | {} | |
-| defaultVisible | 初始下拉框是否显示 | Boolean | false | |
-| visible | 当前下拉框是否显示 | Boolean | - | |
-| onVisibleChange | 下拉框显示或关闭时触发事件的回调函数
**签名**:
Function(visible: Boolean, type: String) => void
**参数**:
_visible_: {Boolean} 是否显示
_type_: {String} 触发显示关闭的操作类型 | Function | () => {} | |
-| popupStyle | 下拉框自定义样式对象 | Object | - | |
-| popupClassName | 下拉框样式自定义类名 | String | - | |
-| popupContainer | 下拉框挂载的容器节点 | any | - | |
-| popupProps | 透传到 Popup 的属性对象 | Object | - | |
-| followTrigger | 是否跟随滚动 | Boolean | - | |
-| isPreview | 是否为预览态 | Boolean | - | |
-| renderPreview | 预览态模式下渲染的内容
**签名**:
Function(value: Array) => void
**参数**:
_value_: {Array} 选择值 { label: , value:} | Function | - | |
-| useVirtual | 是否开启虚拟滚动 | Boolean | false | |
-| immutable | 是否是不可变数据 | Boolean | - | 1.23 |
-| clickToCheck | 点击文本是否可以勾选 | Boolean | false | |
-| valueRender | 渲染 Select 展现内容的方法
**签名**:
Function(item: Object, itemPaths: Array) => ReactNode
**参数**:
_item_: {Object} 渲染节点的item
_itemPaths_: {Array} item的全路径数组
**返回值**:
{ReactNode} 展现内容
| Function | item => `item.label | | item.value` | |
-| useDetailValue | onChange 第一个参数返回 dataSource 中的对象 | Boolean | - | |
+| 参数 | 说明 | 类型 | 默认值 | 是否必填 | 支持版本 |
+| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------- | -------- |
+| children | 树节点 | React.ReactNode | - | | - |
+| size | 选择框大小 | 'small' \| 'medium' \| 'large' | 'medium' | | - |
+| placeholder | 选择框占位符 | string | - | | - |
+| disabled | 是否禁用 | boolean | false | | - |
+| hasArrow | 是否有下拉箭头 | boolean | true | | - |
+| hasBorder | 是否有边框 | boolean | true | | - |
+| hasClear | 是否有清空按钮 | boolean | true | | - |
+| label | 自定义内联 label | React.ReactNode | - | | - |
+| readOnly | 是否只读,只读模式下可以展开弹层但不能选择 | boolean | - | | - |
+| autoWidth | 下拉框是否与选择器对齐 | boolean | true | | - |
+| dataSource | 数据源,该属性优先级高于 children | DataSourceItem[] | - | | - |
+| value | (受控)当前值 | DataSourceItem[] \| DataSourceItem | - | | - |
+| defaultValue | (非受控)默认值 | SelectProps['defaultValue'] | null | | - |
+| preserveNonExistentValue | value/defaultValue 在 dataSource 中不存在时,是否展示 | boolean | false | | 1.25 |
+| onChange | 选中值改变时触发的回调函数 | (
value: DataSourceItem[] \| DataSourceItem,
data: ObjectItem[] \| ObjectItem \| null
) => void | () =\> \{\} | | - |
+| tagInline | 是否一行显示,仅在 multiple 和 treeCheckable 为 true 时生效 | boolean | false | | 1.25 |
+| maxTagPlaceholder | 隐藏多余 tag 时显示的内容,在 tagInline 生效时起作用
**签名**:
**参数**:
_selectedValues_: 当前已选中的元素
_totalValues_: 总待选元素,treeCheckedStrategy = 'parent' 时为 undefined
**返回值**:
ReactNode \| HTMLElement | (
selectedValues: ObjectItem[],
totalValues?: ObjectItem[]
) => React.ReactNode \| HTMLElement | - | | 1.25 |
+| autoClearSearch | 是否自动清除 searchValue | boolean | true | | 1.26 |
+| showSearch | 是否显示搜索框 | boolean | false | | - |
+| onSearch | 在搜索框中输入时触发的回调函数 | (keyword: string) => void | () =\> \{\} | | - |
+| notFoundContent | 无数据时显示内容 | React.ReactNode | 'Not Found' | | - |
+| multiple | 是否支持多选 | boolean | false | | - |
+| treeCheckable | 下拉框中的树是否支持勾选节点的复选框 | boolean | false | | - |
+| treeCheckStrictly | 下拉框中的树勾选节点复选框是否完全受控(父子节点选中状态不再关联) | boolean | false | | - |
+| treeCheckedStrategy | 定义选中时回填的方式 | 'all' \| 'parent' \| 'child' | 'parent' | | - |
+| treeDefaultExpandAll | 下拉框中的树是否默认展开所有节点 | boolean | false | | - |
+| treeDefaultExpandedKeys | 下拉框中的树默认展开节点key的数组 | Array\ | [] | | - |
+| treeLoadData | 下拉框中的树异步加载数据的函数,使用请参考[Tree的异步加载数据Demo](https://fusion.design/pc/component/basic/tree#%E5%BC%82%E6%AD%A5%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE) | TreeProps['loadData'] | - | | - |
+| treeProps | 透传到 Tree 的属性对象 | TreeProps | \{\} | | - |
+| defaultVisible | 初始下拉框是否显示 | boolean | false | | - |
+| visible | 当前下拉框是否显示 | boolean | - | | - |
+| onVisibleChange | 下拉框显示或关闭时触发事件的回调函数 | (visible: boolean, type: string) => void | () =\> \{\} | | - |
+| popupStyle | 下拉框自定义样式对象 | React.CSSProperties | - | | - |
+| popupClassName | 下拉框样式自定义类名 | string | - | | - |
+| popupContainer | 下拉框挂载的容器节点 | string \| HTMLElement \| ((target: HTMLElement) => HTMLElement) | - | | - |
+| popupProps | 透传到 Popup 的属性对象 | PopupProps | - | | - |
+| followTrigger | 是否跟随滚动 | boolean | - | | - |
+| isPreview | 是否为预览态 | boolean | - | | - |
+| renderPreview | 预览态模式下渲染的内容 | (data: ObjectItem[], props: TreeSelectProps) => React.ReactNode | - | | - |
+| useVirtual | 是否开启虚拟滚动 | boolean | false | | - |
+| filterLocal | 是否关闭本地搜索 | boolean | true | | - |
+| immutable | 是否是不可变数据 | boolean | - | | 1.23 |
+| clickToCheck | 点击文本是否可以勾选 | boolean | false | | - |
+| valueRender | 渲染 Select 区域展现内容的方法
**签名**:
**参数**:
_item_: 渲染项
_itemPaths_: 渲染项在dataSource内的路径
**返回值**:
ReactNode - 展现内容 | (item: TreeSelectState['\_k2n'][Key], itemPaths: ObjectItem[]) => React.ReactNode | (item) =\> item.label \|\| item.value | | - |
diff --git a/components/tree-select/__docs__/theme/index.jsx b/components/tree-select/__docs__/theme/index.tsx
similarity index 53%
rename from components/tree-select/__docs__/theme/index.jsx
rename to components/tree-select/__docs__/theme/index.tsx
index d983e28477..72df33d2ad 100644
--- a/components/tree-select/__docs__/theme/index.jsx
+++ b/components/tree-select/__docs__/theme/index.tsx
@@ -2,8 +2,14 @@ import React from 'react';
import ReactDOM from 'react-dom';
import '../../../demo-helper/style';
import '../../style';
-import { Demo, DemoHead, DemoGroup, initDemo } from '../../../demo-helper';
-import TreeSelect from '../../index';
+import {
+ Demo,
+ DemoHead,
+ DemoGroup,
+ initDemo,
+ type DemoFunctionDefineForObject,
+} from '../../../demo-helper';
+import TreeSelect, { type TreeSelectProps } from '../../index';
import zhCN from '../../../locale/zh-cn';
import enUS from '../../../locale/en-us';
import ConfigProvider from '../../../config-provider';
@@ -15,68 +21,83 @@ const i18nMap = {
trunk: '树干',
branch: '数枝',
leaf: '叶子',
- label: '标签:'
+ label: '标签:',
},
'en-us': {
trunk: 'Trunk',
branch: 'Branch',
leaf: 'Leaf',
- label: 'Label'
- }
+ label: 'Label',
+ },
};
-class FunctionDemo extends React.Component {
- constructor(props) {
+interface FunctionGroupButtonProps {
+ i18n: Record;
+ treeCheckable?: boolean;
+}
+
+class FunctionDemo extends React.Component<
+ FunctionGroupButtonProps,
+ {
+ demoFunction: Record;
+ }
+> {
+ constructor(props: FunctionGroupButtonProps) {
super(props);
this.state = {
demoFunction: {
hasBorder: {
label: '边框',
value: 'true',
- enum: [{
- label: '显示',
- value: 'true'
- }, {
- label: '隐藏',
- value: 'false'
- }]
+ enum: [
+ {
+ label: '显示',
+ value: 'true',
+ },
+ {
+ label: '隐藏',
+ value: 'false',
+ },
+ ],
},
inlineLabel: {
label: '是否内置标签',
value: 'false',
- enum: [{
- label: '显示',
- value: 'true'
- }, {
- label: '隐藏',
- value: 'false'
- }]
- }
- }
+ enum: [
+ {
+ label: '显示',
+ value: 'true',
+ },
+ {
+ label: '隐藏',
+ value: 'false',
+ },
+ ],
+ },
+ },
};
this.onFunctionChange = this.onFunctionChange.bind(this);
}
- onFunctionChange(demoFunction) {
+ onFunctionChange(demoFunction: Record) {
this.setState({
- demoFunction
+ demoFunction,
});
}
render() {
console.log(this.state.demoFunction);
- // eslint-disable-next-line
const { treeCheckable, i18n } = this.props;
const { demoFunction } = this.state;
const hasBorder = demoFunction.hasBorder.value === 'true';
const inlineLabel = demoFunction.inlineLabel.value === 'true';
- const treeSelectProps = {
+ const treeSelectProps: TreeSelectProps = {
treeDefaultExpandAll: true,
treeCheckable,
hasBorder,
style: { width: '200px' },
- popupContainer: target => target.parentNode,
+ popupContainer: (target: HTMLElement) => target.parentNode as HTMLElement,
children: (
@@ -90,14 +111,18 @@ class FunctionDemo extends React.Component {
- )
+ ),
};
if (inlineLabel) {
treeSelectProps.label = i18n.label;
}
return (
-
+
@@ -106,9 +131,30 @@ class FunctionDemo extends React.Component {
-
-
-
+
+
+
@@ -121,16 +167,17 @@ class FunctionDemo extends React.Component {
}
}
-window.renderDemo = function(lang = 'en-us') {
+window.renderDemo = function (lang = 'en-us') {
const i18n = i18nMap[lang];
- ReactDOM.render((
+ ReactDOM.render(
-
- ), document.getElementById('container'));
+ ,
+ document.getElementById('container')
+ );
};
window.renderDemo();
diff --git a/components/tree-select/__tests__/index-spec.js b/components/tree-select/__tests__/index-spec.tsx
similarity index 52%
rename from components/tree-select/__tests__/index-spec.js
rename to components/tree-select/__tests__/index-spec.tsx
index 3d68238217..846caa1788 100644
--- a/components/tree-select/__tests__/index-spec.js
+++ b/components/tree-select/__tests__/index-spec.tsx
@@ -1,22 +1,14 @@
import React, { useState } from 'react';
-import ReactTestUtils from 'react-dom/test-utils';
-import Enzyme, { mount } from 'enzyme';
-import Adapter from 'enzyme-adapter-react-16';
-import assert from 'power-assert';
-import { dom, KEYCODE } from '../../util';
+import { debounce } from 'lodash';
+import { KEYCODE } from '../../util';
import TreeSelect from '../index';
import '../style';
-
-/* eslint-disable react/jsx-filename-extension */
-/* global describe it afterEach */
-/* global describe it beforeEach */
-
-Enzyme.configure({ adapter: new Adapter() });
+import type { DataSourceItem, ObjectItem } from '../../select';
+import type { TreeSelectDataItem } from '../types';
const TreeNode = TreeSelect.Node;
-const { hasClass } = dom;
-const dataSource = [
+const dataSource: TreeSelectDataItem[] = [
{
label: '服装',
className: 'k-1',
@@ -56,28 +48,29 @@ const dataSource = [
},
];
-function freeze(dataSource) {
+function freeze(dataSource: TreeSelectDataItem[]) {
return Object.freeze([
...dataSource.map(item => {
const { children } = item;
- item.children = children && freeze(children);
+ item.children =
+ children && (freeze(children as TreeSelectDataItem[]) as TreeSelectDataItem[]);
return Object.freeze({ ...item });
}),
- ]);
+ ]) as TreeSelectDataItem[];
}
-function cloneData(data, valueMap = {}) {
- const loop = data =>
+function cloneData(data: TreeSelectDataItem[], valueMap: Record = {}) {
+ const loop = (data: TreeSelectDataItem[]) =>
data.map(item => {
- let newItem;
+ let newItem: TreeSelectDataItem;
- if (item.value in valueMap) {
- newItem = { ...item, ...valueMap[item.value] };
+ if ((item.value as string) in valueMap) {
+ newItem = { ...item, ...valueMap[item.value as string] };
} else {
newItem = { ...item };
}
if (newItem.children) {
- newItem.children = loop(newItem.children);
+ newItem.children = loop(newItem.children as TreeSelectDataItem[]);
}
return newItem;
@@ -86,13 +79,13 @@ function cloneData(data, valueMap = {}) {
return loop(data);
}
-function flattenData(dataSource) {
- const flattenList = [];
- const drill = data => {
+function flattenData(dataSource: ObjectItem[]) {
+ const flattenList: ObjectItem[] = [];
+ const drill = (data: ObjectItem[]) => {
data.forEach(item => {
const { children, ...newItem } = item;
flattenList.push(newItem);
- children && children.length && drill(children);
+ children && children.length && drill(children as ObjectItem[]);
});
};
@@ -101,162 +94,180 @@ function flattenData(dataSource) {
return flattenList;
}
-function assertDataAndNodes(dataSource) {
- const labels = Array.prototype.map.call(
- document.querySelectorAll('li.next-tree-node .next-tree-node-label'),
- item => item.textContent
- );
-
- assert(flattenData(dataSource).every((item, index) => item.label === labels[index]));
+function shouldDataAndNodes(dataSource: ObjectItem[]) {
+ cy.get('li.next-tree-node .next-tree-node-label').then($el => {
+ flattenData(dataSource).every((item, index) =>
+ expect(item.label).to.equal($el[index].textContent?.trim())
+ );
+ });
}
-function findTreeNodeByValue(value, container = document) {
- return container.querySelector(`.k-${value}`);
+function findTreeNodeByValue(value: string) {
+ return cy.get(`.k-${value}`);
}
-function createMap(data) {
- const map = {};
+function createMap(data: ObjectItem[]) {
+ const map: Record = {};
- const loop = (data, prefix = '0') => {
+ const loop = (data: ObjectItem[], prefix = '0') => {
data.forEach((item, index) => {
const { value, label, children, ...rests } = item;
const pos = `${prefix}-${index}`;
- map[value] = { ...rests, value, label, pos, key: pos };
+ map[value as string] = { ...rests, value, label, pos, key: pos };
if (children && children.length) {
- loop(children, pos);
+ loop(children as ObjectItem[], pos);
}
- })
- }
+ });
+ };
loop(data);
return map;
}
-function selectTreeNode(value, container) {
- ReactTestUtils.Simulate.click(findTreeNodeByValue(value, container).querySelector('.next-tree-node-label'));
+function selectTreeNode(value: string) {
+ findTreeNodeByValue(value).find('.next-tree-node-label').first().click();
}
-function checkTreeNode(value) {
- const input = findTreeNodeByValue(value).querySelector('.next-checkbox input');
- ReactTestUtils.Simulate.click(input);
+function checkTreeNode(value: string) {
+ findTreeNodeByValue(value).find('.next-checkbox input').click();
}
-function assertSelected(value, selected, container) {
- assert(hasClass(findTreeNodeByValue(value, container).querySelector('.next-tree-node-inner'), 'next-selected') === selected);
+function shouldSelected(value: string, selected: boolean) {
+ findTreeNodeByValue(value)
+ .find('.next-tree-node-inner')
+ .should(selected ? 'have.class' : 'not.have.class', 'next-selected');
}
-function assertChecked(value, checked) {
- assert(hasClass(findTreeNodeByValue(value).querySelector('.next-checkbox-wrapper'), 'checked') === checked);
+function shouldChecked(value: string, checked: boolean) {
+ findTreeNodeByValue(value)
+ .find('.next-checkbox-wrapper')
+ .should(checked ? 'have.class' : 'not.have.class', 'checked');
}
-function getLabels(wrapper) {
- return wrapper.find('span.next-tag-body').map(node => node.text().trim());
+function getLabels() {
+ return cy.get('span.next-tag-body').then($el => {
+ return $el.map((index, el) => {
+ return Cypress.$(el).text().trim();
+ });
+ });
}
+function shouldHideElement() {
+ cy.document().then(document => {
+ const overlay = document.querySelector('.next-overlay-wrapper');
+ if (overlay) {
+ cy.wrap(overlay).should($el => {
+ expect($el).to.have.css('display', 'none');
+ });
+ } else {
+ expect(overlay).to.be.null;
+ }
+ });
+}
-const _v2n = createMap(dataSource);
-
-describe('TreeSelect', () => {
- let wrapper;
-
- beforeEach(() => {
- const nodeListArr = [].slice.call(document.querySelectorAll('.next-overlay-wrapper'));
-
- nodeListArr.forEach(node => {
- node.parentNode.removeChild(node);
+function shouldShowElement() {
+ cy.document().then(document => {
+ const overlay = document.querySelector('.next-overlay-wrapper');
+ expect(overlay).to.not.be.null;
+ cy.wrap(overlay).should($el => {
+ expect($el).to.not.have.css('display', 'none');
});
});
+}
- afterEach(() => {
- if (wrapper) {
- wrapper.unmount();
- wrapper = null;
- }
- });
+const _v2n = createMap(dataSource);
- it('should show dropdown when click select box', done => {
- wrapper = mount();
- wrapper.find('.next-select').simulate('click');
- setTimeout(() => {
- assert(document.querySelector('.next-tree-select-dropdown'));
- done();
- }, 1000);
+describe('TreeSelect', () => {
+ it('should show dropdown when click select box', () => {
+ cy.mount();
+ cy.get('.next-select').trigger('click');
+ cy.get('.next-tree-select-dropdown').should('exist');
});
it('should show dropdown when set defaultVisible to true', () => {
- wrapper = mount();
- assert(document.querySelector('.next-tree-select-dropdown'));
+ cy.mount();
+ cy.get('.next-tree-select-dropdown').should('exist');
});
it('should render by loop TreeNode', () => {
- const loop = data =>
+ const loop = (data: ObjectItem[]) =>
data.map(item => {
return (
-
- {item.children ? loop(item.children) : null}
+
+ {item.children ? loop(item.children as ObjectItem[]) : null}
);
});
- wrapper = mount(
+ cy.mount(
{loop(dataSource)}
- );
- assertDataAndNodes(dataSource);
+ ).as('Demo');
+ shouldDataAndNodes(dataSource);
const newDataSource = [...dataSource];
newDataSource.push({
label: '鞋',
value: '7',
});
- wrapper.setProps({
+
+ cy.rerender('Demo', {
children: loop(newDataSource),
});
- assertDataAndNodes(newDataSource);
+ shouldDataAndNodes(newDataSource);
});
it('should render by dataSource', () => {
- wrapper = mount();
- assertDataAndNodes(dataSource);
+ cy.mount().as(
+ 'Demo'
+ );
+ shouldDataAndNodes(dataSource);
const newDataSource = [...dataSource];
newDataSource.push({
label: '鞋',
value: '7',
});
- wrapper.setProps({
- dataSource: newDataSource,
- });
- assertDataAndNodes(newDataSource);
+ cy.rerender('Demo', { dataSource: newDataSource });
+ shouldDataAndNodes(newDataSource);
});
it('should render by defaultValue', () => {
- wrapper = mount();
- assertSelected('4', true);
+ cy.mount(
+
+ ).as('Demo');
+ shouldSelected('4', true);
- wrapper.setProps({ defaultValue: '6' });
- wrapper.update();
- assertSelected('6', false);
+ cy.rerender('Demo', {
+ defaultValue: '6',
+ });
+ shouldSelected('6', false);
});
it('should render by detail defaultValue', () => {
- wrapper = mount(
+ cy.mount(
- );
- assertSelected('4', true);
+ ).as('Demo');
+ shouldSelected('4', true);
- wrapper.setProps({ defaultValue: { label: '裙子', value: '6' } });
- wrapper.update();
- assertSelected('6', false);
+ cy.rerender('Demo', {
+ defaultValue: { label: '裙子', value: '6' },
+ });
+ shouldSelected('6', false);
});
it('should render by value', () => {
- wrapper = mount(
+ cy.mount(
{
treeDefaultExpandAll
dataSource={dataSource}
/>
- );
- assertSelected('4', false);
- assertSelected('6', true);
+ ).as('Demo');
+ shouldSelected('4', false);
+ shouldSelected('6', true);
const newValue = ['4', '6'];
- wrapper.setProps({ value: newValue });
- wrapper.update();
- assertSelected('4', true);
- assertSelected('6', true);
+ cy.rerender('Demo', {
+ value: newValue,
+ });
+ shouldSelected('4', true);
+ shouldSelected('6', true);
});
it('should render by detail value', () => {
- wrapper = mount(
+ cy.mount(
{
treeDefaultExpandAll
dataSource={dataSource}
/>
- );
- assertSelected('4', false);
- assertSelected('6', true);
-
+ ).as('Demo');
+ shouldSelected('4', false);
+ shouldSelected('6', true);
const newValue = [
{ label: '外套', value: '4' },
{ label: '裙子', value: '6' },
];
- wrapper.setProps({ value: newValue });
- wrapper.update();
- assertSelected('4', true);
- assertSelected('6', true);
+ cy.rerender('Demo', {
+ value: newValue,
+ });
+ shouldSelected('4', true);
+ shouldSelected('6', true);
});
it('should render by defaultValue when enable treeCheckable', () => {
- wrapper = mount(
-
- );
- assertChecked('4', true);
-
- wrapper.setProps({ defaultValue: '6' });
- wrapper.update();
- assertChecked('6', false);
+ cy.mount(
+
+ ).as('Demo');
+ shouldChecked('4', true);
+ cy.rerender('Demo', {
+ defaultValue: '6',
+ });
+ shouldChecked('6', false);
});
it('should render by value when enable treeCheckable', () => {
- wrapper = mount(
+ cy.mount(
{
treeDefaultExpandAll
dataSource={dataSource}
/>
- );
- assertChecked('4', false);
- assertChecked('6', true);
-
+ ).as('Demo');
+ shouldChecked('4', false);
+ shouldChecked('6', true);
const newValue = ['4', '6'];
- wrapper.setProps({ value: newValue });
- wrapper.update();
- assertChecked('4', true);
- assertChecked('6', true);
+ cy.rerender('Demo', {
+ value: newValue,
+ });
+ shouldChecked('4', true);
+ shouldChecked('6', true);
});
- it('should trigger onChange and close dropdown when select tree node', done => {
- let triggered = false;
+ it('should trigger onChange and close dropdown when select tree node', () => {
+ const onClick = cy.spy();
+
const expectValue = '4';
const expectItem = _v2n[expectValue];
- const handleChange = (value, data) => {
- triggered = true;
- assert(value === expectValue);
- assert.deepEqual(data, expectItem);
+ const handleChange = (
+ value: DataSourceItem | DataSourceItem[],
+ data: ObjectItem | ObjectItem[] | null
+ ) => {
+ onClick();
+ expect(value).to.equal(expectValue);
+ expect(data).to.deep.equal(expectItem);
};
- wrapper = mount(
-
+ cy.mount(
+
);
selectTreeNode(expectValue);
- wrapper.update();
- assert(triggered);
-
- setTimeout(() => {
- assert(
- !document.querySelector('.next-overlay-wrapper') ||
- document.querySelector('.next-overlay-wrapper').style.display === 'none'
- );
- done();
- }, 1000);
+ cy.wrap(onClick).should('be.calledOnce');
+ shouldHideElement();
});
- it('should not trigger onChange but close dropdown when select selected node', done => {
- let triggered = false;
+ it('should not trigger onChange but close dropdown when select selected node', () => {
+ const onClick = cy.spy();
const value = '4';
const handleChange = () => {
- triggered = true;
+ onClick();
};
- wrapper = mount(
+ cy.mount(
{
/>
);
selectTreeNode(value);
- wrapper.update();
- assert(!triggered);
-
- setTimeout(() => {
- assert(
- !document.querySelector('.next-overlay-wrapper') ||
- document.querySelector('.next-overlay-wrapper').style.display === 'none'
- );
- done();
- }, 1000);
+ cy.wrap(onClick).should('not.be.called');
+ shouldHideElement();
});
- it('should trigger onChange but not close dropdown when select node and enable multiple', done => {
- let triggered = false;
+ it('should trigger onChange but not close dropdown when select node and enable multiple', () => {
+ const onClick = cy.spy();
const initValue = '4';
const appendValue = '6';
const expectValue = [initValue, appendValue];
- const handleChange = (value, data) => {
- triggered = true;
- assert.deepEqual(value, expectValue);
- assert.deepEqual(data, expectValue.map(v => _v2n[v]));
+ const handleChange = (
+ value: DataSourceItem | DataSourceItem[],
+ data: ObjectItem | ObjectItem[] | null
+ ) => {
+ onClick();
+ expect(value).to.deep.equal(expectValue);
+ expect(data).to.deep.equal(expectValue.map(v => _v2n[v]));
};
- wrapper = mount(
+ cy.mount(
{
/>
);
selectTreeNode(appendValue);
- wrapper.update();
- assert(triggered);
-
- setTimeout(() => {
- assert(
- document.querySelector('.next-overlay-wrapper') &&
- document.querySelector('.next-overlay-wrapper').style.display !== 'none'
- );
- done();
- }, 1000);
+ cy.wrap(onClick).should('be.calledOnce');
+ shouldShowElement();
});
it('should trigger onChange when check node', () => {
- let triggered = false;
+ const onClick = cy.spy();
const initValue = '4';
const appendValue = '6';
const expectValue = ['4', '3'];
- const handleChange = (value, data) => {
- triggered = true;
- assert.deepEqual(value, expectValue);
- assert.deepEqual(data, expectValue.map(v => _v2n[v]));
+ const handleChange = (
+ value: DataSourceItem | DataSourceItem[],
+ data: ObjectItem | ObjectItem[] | null
+ ) => {
+ onClick();
+ expect(value).to.deep.equal(expectValue);
+ expect(data).to.deep.equal(expectValue.map(v => _v2n[v]));
};
- wrapper = mount(
+ cy.mount(
{
value={initValue}
onChange={handleChange}
/>
- );
- checkTreeNode(appendValue);
- wrapper.update();
- assert(triggered);
+ ).then(() => {
+ checkTreeNode(appendValue);
+ cy.wrap(onClick).should('be.calledOnce');
+ });
});
it('should trigger onChange when check node and enable treeCheckStrictly', () => {
- let triggered = false;
+ const onClick = cy.spy();
const appendValue = '6';
const expectValue = [appendValue];
- const handleChange = (value, data) => {
- triggered = true;
- assert.deepEqual(value, expectValue);
- assert.deepEqual(data, expectValue.map(v => _v2n[v]));
+ const handleChange = (
+ value: DataSourceItem | DataSourceItem[],
+ data: ObjectItem | ObjectItem[] | null
+ ) => {
+ onClick();
+ expect(value).to.deep.equal(expectValue);
+ expect(data).to.deep.equal(expectValue.map(v => _v2n[v]));
};
- wrapper = mount(
+ cy.mount(
{
dataSource={dataSource}
onChange={handleChange}
/>
- );
- checkTreeNode(appendValue);
- wrapper.update();
- assert(triggered);
+ ).then(() => {
+ checkTreeNode(appendValue);
+ cy.wrap(onClick).should('be.calledOnce');
+ });
});
it('should render tag when defaultValue [{ label, value}]', () => {
- wrapper = mount(
+ cy.mount(
{
/>
);
- assert.deepEqual(getLabels(wrapper), ['test1']);
+ getLabels().then($labels => {
+ expect($labels.get()).to.deep.equal(['test1']);
+ });
});
it('should set parent node checked if all child nodes is checked even treeCheckedStrategy is "child"', () => {
- wrapper = mount(
+ cy.mount(
{
/>
);
- assertChecked('3', true);
+ shouldChecked('3', true);
});
it('should render parent tag when set treeCheckedStrategy to all', () => {
- wrapper = mount(
+ cy.mount(
{
treeCheckedStrategy="parent"
/>
);
- assert.deepEqual(getLabels(wrapper), ['女装']);
+ getLabels().then($labels => {
+ expect($labels.get()).to.deep.equal(['女装']);
+ });
});
it('should render child tag when set treeCheckedStrategy to all', () => {
- wrapper = mount(
-
+ cy.mount(
+
);
- assert.deepEqual(getLabels(wrapper), ['裙子']);
+ getLabels().then($labels => {
+ expect($labels.get()).to.deep.equal(['裙子']);
+ });
});
it('should render all tag when set treeCheckedStrategy to all', () => {
- wrapper = mount(
+ cy.mount(
{
treeCheckedStrategy="all"
/>
);
- assert.deepEqual(getLabels(wrapper), ['女装', '裙子']);
-
- wrapper
- .find('div.next-tag')
- .at(0)
- .find('.next-icon-close')
- .simulate('click');
- wrapper.update();
- assert.deepEqual(getLabels(wrapper), []);
+
+ getLabels().then($labels => {
+ expect($labels.get()).to.deep.equal(['女装', '裙子']);
+ });
+
+ cy.get('div.next-tag').eq(0).find('.next-icon-close').click();
+ cy.get('span.next-tag-body').should('not.exist');
});
it('should support preview mode render', () => {
@@ -576,29 +597,38 @@ describe('TreeSelect', () => {
},
];
- wrapper = mount();
- assert(wrapper.find('.next-form-preview').length > 0);
- assert(wrapper.find('.next-form-preview').text() === '西安市');
- wrapper.setProps({
- renderPreview: items => {
- assert(items.length === 1);
- assert(items[0].label === '西安市');
+ cy.mount().as('Demo');
+ cy.get('.next-form-preview')
+ .should('exist')
+ .and($el => {
+ expect($el.text()).to.equal('西安市');
+ });
+
+ cy.rerender('Demo', {
+ renderPreview: (items: ObjectItem[]) => {
+ expect(items.length).to.equal(1);
+ expect(items[0].label).to.equal('西安市');
return 'Hello World';
},
});
- assert(wrapper.find('.next-form-preview').text() === 'Hello World');
+ cy.get('.next-form-preview').then($el => {
+ expect($el.text()).to.equal('Hello World');
+ });
});
it('should trigger onChange when remove tag', () => {
- let triggered = false;
+ const onClick = cy.spy();
const value = ['6'];
- const handleChange = (value, data) => {
- triggered = true;
- assert.deepEqual(value, []);
- assert.deepEqual(data, []);
+ const handleChange = (
+ value: ObjectItem | ObjectItem[],
+ data: ObjectItem | ObjectItem[]
+ ) => {
+ onClick();
+ expect(value).to.deep.equal([]);
+ expect(data).to.deep.equal([]);
};
- wrapper = mount(
+ cy.mount(
{
onChange={handleChange}
/>
);
- wrapper.find('.next-icon-close').simulate('click');
- wrapper.update();
- assert(triggered);
+ cy.get('.next-icon-close').click();
+ cy.wrap(onClick).should('be.called');
});
- it('should support multiple with hasClear', done => {
- wrapper = mount(
+ it('should support multiple with hasClear', () => {
+ cy.mount(
{
- assert(value === null);
- done();
+ expect(value).to.equal(null);
}}
/>
);
-
- wrapper.find('i.next-icon-delete-filling').simulate('click');
+ cy.get('i.next-icon-delete-filling').click({ force: true });
});
it('should trigger onSearch when search some keyword', () => {
- let triggered = false;
+ const onClick = cy.spy();
const searchedValue = '外套';
- const handleSearch = value => {
- triggered = true;
- assert(value === searchedValue);
+ const handleSearch = (value: string) => {
+ onClick();
+ expect(value).to.equal(searchedValue);
};
- wrapper = mount(
+ cy.mount(
);
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: '外套' } });
- wrapper.update();
- assert(triggered);
+ cy.get('.next-select-trigger-search input').type('外套');
+ cy.wrap(onClick).should('be.calledOnce');
});
it('should hightlight matched node when search some keyword', () => {
- wrapper = mount(
+ cy.mount(
{
);
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: 'element' } });
- wrapper.update();
- const node = document.querySelector('.react-element');
- assert(hasClass(node, 'next-filtered'));
+ cy.get('.next-select-trigger-search input').type('element');
+ cy.get('.react-element').should('have.class', 'next-filtered');
});
it('should ignore case when search', () => {
@@ -692,43 +716,51 @@ describe('TreeSelect', () => {
],
},
];
- wrapper = mount();
+ cy.mount(
+
+ );
['INPUT', 'input'].forEach(kw => {
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: kw } });
- wrapper.update();
+ cy.get('.next-select-trigger-search input').clear(); // Need to clear first
+ cy.get('.next-select-trigger-search input').type(kw);
- const node = document.querySelector('.next-filtered');
- assert(node && node.querySelector('.next-tree-node-label') !== 'Input');
+ cy.get('.next-filtered').find('.next-tree-node-label').should('have.text', 'Input');
});
});
// https://github.com/alibaba-fusion/next/issues/2029
it('fix bug after setState onSearch', () => {
function Demo() {
- const [data, setData] = useState([{ label: 'element', key: '0', className: 'react-element' }]);
+ const [data, setData] = useState([
+ { label: 'element', key: '0', className: 'react-element' },
+ ]);
function handleChange() {
setData([{ label: 'react-element-new', key: '1', className: 'react-element-new' }]);
}
return (
-
+
);
}
- wrapper = mount();
-
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: 'element' } });
- wrapper.update();
+ cy.mount();
- assert(!hasClass(document.querySelector('.react-element'), 'next-filtered'));
- assert(hasClass(document.querySelector('.react-element-new'), 'next-filtered'));
+ cy.get('.next-select-trigger-search input').type('element');
+ cy.get('.react-element').should('not.exist');
+ cy.get('.react-element-new').should('have.class', 'next-filtered');
});
it('should only show matched node and its parent node when search some keyword', () => {
- wrapper = mount();
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: '外套' } });
- wrapper.update();
+ cy.mount(
+
+ );
+ cy.get('.next-select-trigger-search input').type('外套');
const expectTreeData = [
{
@@ -748,24 +780,23 @@ describe('TreeSelect', () => {
],
},
];
- assertDataAndNodes(expectTreeData);
+ shouldDataAndNodes(expectTreeData);
- ['3', '5'].forEach(v => assert(findTreeNodeByValue(v).style.display === 'none'));
+ ['3', '5'].forEach(v => findTreeNodeByValue(v).should('have.css', { display: 'none' }));
});
it('should support search well when use virtual', () => {
const data = cloneData(dataSource);
- data[0].children = data[0].children.concat(
- new Array(100).fill().map((__, index) => {
- index = String(index);
+ data[0].children = (data[0].children as TreeSelectDataItem[]).concat(
+ new Array(100).fill(null).map((__, index) => {
return {
- value: index,
- label: index,
+ value: String(index),
+ label: String(index),
};
})
);
- wrapper = mount(
+ cy.mount(
{
}}
/>
);
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: 77 } });
- wrapper.update();
+ cy.get('.next-select-trigger-search input').type('77');
const expectTreeData = [
{
@@ -793,23 +823,22 @@ describe('TreeSelect', () => {
],
},
];
- assertDataAndNodes(expectTreeData);
+ shouldDataAndNodes(expectTreeData);
});
// https://github.com/alibaba-fusion/next/issues/2271
it('fix search bug when useVirtual', () => {
const data = cloneData(dataSource);
- data[0].children = data[0].children.concat(
- new Array(100).fill().map((__, index) => {
- index = String(index);
+ data[0].children = (data[0].children as TreeSelectDataItem[]).concat(
+ new Array(100).fill(null).map((__, index) => {
return {
- value: index,
- label: index,
+ value: String(index),
+ label: String(index),
};
})
);
- wrapper = mount(
+ cy.mount(
{
}}
/>
);
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: 77 } });
+ cy.get('.next-select-trigger-search input').type('77');
- wrapper.find('.next-tree-node[value="77"] input').simulate('click');
- wrapper.update();
+ cy.get('.next-tree-node[value="77"] input').click();
- assert(wrapper.find('.next-tree-node[value="1"] .indeterminate').length);
+ cy.get('.next-tree-node[value="1"] .indeterminate').should('exist');
});
it('should render not found if dataSource is empty or there is no search result', () => {
- wrapper = mount();
- assert(document.querySelector('.next-tree-select-not-found').textContent.trim() === 'Not Found');
+ cy.mount().as('Demo');
+ cy.get('.next-tree-select-not-found').should('have.text', 'Not Found');
+ cy.rerender('Demo', { dataSource });
+ cy.get('.next-tree').should('exist');
- wrapper.setProps({ dataSource });
- wrapper.update();
- assert(document.querySelector('.next-tree'));
-
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: '哈哈' } });
- wrapper.update();
- assert(document.querySelector('.next-tree-select-not-found').textContent.trim() === 'Not Found');
+ cy.get('.next-select-trigger-search input').type('哈哈');
+ cy.get('.next-tree-select-not-found').should('have.text', 'Not Found');
});
it('should turn off local search when filterLocal is false', () => {
- wrapper = mount(
+ cy.mount(
{
/>
);
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: '外套' } });
- wrapper.update();
+ cy.get('.next-select-trigger-search input').type('外套');
- assertDataAndNodes(dataSource);
+ shouldDataAndNodes(dataSource);
});
it('should not clear search value when autoClearSearch is false', () => {
- wrapper = mount(
-
+ cy.mount(
+
);
- wrapper.find('.next-select-trigger-search input').simulate('change', { target: { value: '外套' } });
- wrapper.find('.next-tree-node[value="4"]').simulate('click');
- wrapper.update();
+ cy.get('.next-select-trigger-search input').type('外套');
+ cy.get('.next-tree-node[value="4"]').click();
- assert(wrapper.find('.next-select-trigger-search input').prop('value') === '外套');
+ cy.get('.next-select-trigger-search input').should('have.value', '外套');
});
it('fix issues use isPreview when value is empty', () => {
- wrapper = mount();
- assert(wrapper.find('.next-form-preview').instance().textContent === '');
+ cy.mount();
+ cy.get('.next-form-preview').should('have.text', '');
});
it('should support immutable ', () => {
- wrapper = mount();
- assertDataAndNodes(dataSource);
+ cy.mount(
+
+ );
+ shouldDataAndNodes(dataSource);
});
- it('should support keyboard', done => {
- wrapper = mount(
+ it('should support keyboard', () => {
+ cy.mount(
{
})}
/>
);
- wrapper.find('.next-select').simulate('click');
-
- setTimeout(() => {
- assert(document.querySelector('.next-tree'));
- wrapper.find('.next-select-trigger-search input').simulate('keydown', { keyCode: KEYCODE.DOWN });
- assert(
- document.activeElement ===
- document.querySelectorAll(
- '.next-tree > .next-tree-node > .next-tree-node-inner > .next-tree-node-label-wrapper'
- )[0]
- );
- done();
- }, 2000);
+ cy.get('.next-select').click();
+
+ cy.get('.next-tree').should('exist');
+ cy.get('.next-select-trigger-search input').trigger('keydown', { keyCode: KEYCODE.DOWN });
+ cy.get(
+ '.next-tree > .next-tree-node > .next-tree-node-inner > .next-tree-node-label-wrapper'
+ ).then($el => {
+ cy.document().its('activeElement').should('eq', $el[0]);
+ });
});
it('should support single line display', () => {
- wrapper = mount(
+ cy.mount(
{
/>
);
- assert(wrapper.find('.next-select-tag-compact').length > 0);
- assert(
- wrapper
- .find('.next-select-tag-compact')
- .text()
- .includes('3/6')
- );
+ cy.get('.next-select-tag-compact').should('exist');
+ cy.get('.next-select-tag-compact').should('include.text', '3/6');
});
it('should support valueRender', () => {
- wrapper = mount(
+ cy.mount(
{
}}
/>
);
- assert(wrapper.find('.next-select-values').length > 0);
- assert(
- wrapper
- .find('.next-select-values')
- .text()
- .trim() === '服装/男装'
- );
+ cy.get('.next-select-values').should('exist');
+ cy.contains('.next-select-values', '服装/男装');
});
describe('should support useDetailValue', () => {
it('Support dataSource mode', () => {
- const div = document.createElement('div');
- document.body.appendChild(div);
- const handleChange = value => {
- assert(typeof value === 'object');
- assert(value.value === '1');
+ const handleChange = (value: ObjectItem) => {
+ expect(typeof value).to.equal('object');
+ expect(value.value).to.equal('1');
};
- const wrapper = mount(
+ cy.mount(
{
treeDefaultExpandAll
dataSource={dataSource}
onChange={handleChange}
- />,
- { attachTo: div }
+ />
);
- selectTreeNode('1', div);
- wrapper.unmount();
+ selectTreeNode('1');
});
it('Support children mode', () => {
- const div = document.createElement('div');
- document.body.appendChild(div);
- const handleChange = value => {
- assert(typeof value === 'object');
- assert(value.value === '1');
+ const handleChange = (value: ObjectItem) => {
+ expect(typeof value).to.equal('object');
+ expect(value.value).to.equal('1');
};
- const wrapper = mount(
+ cy.mount(
{
- ,
- { attachTo: div }
+
);
- selectTreeNode('1', div);
- wrapper.unmount();
+ selectTreeNode('1');
});
it('Control mode available', () => {
- const div = document.createElement('div');
- document.body.appendChild(div);
-
function App() {
- const [value, setValue] = useState({ label: 'Component', value: '1' });
+ const [value, setValue] = useState({
+ label: 'Component',
+ value: '1',
+ } as ObjectItem);
return (
{
visible
treeDefaultExpandAll
value={value}
- onChange={v => {
- assert(v && typeof v === 'object' && v.value);
+ onChange={(v: ObjectItem) => {
+ expect(v).to.exist;
+ expect(typeof v).to.equal('object');
+ expect(v.value).to.exist;
setValue(v);
}}
>
@@ -1037,13 +1047,11 @@ describe('TreeSelect', () => {
);
}
- const wrapper = mount(, { attachTo: div });
- assert(wrapper.find('.next-select-values').text().trim() === 'Component');
- selectTreeNode('2', div);
- assert(wrapper.find('.next-select-values').text().trim() === 'Form');
- assertSelected('2', true, div);
- wrapper.unmount();
+ cy.mount();
+ cy.get('.next-select-values').should('include.text', 'Component');
+ selectTreeNode('2');
+ cy.get('.next-select-values').should('include.text', 'Form');
+ shouldSelected('2', true);
});
});
-
});
diff --git a/components/tree-select/index.d.ts b/components/tree-select/index.d.ts
deleted file mode 100644
index ebff6e311d..0000000000
--- a/components/tree-select/index.d.ts
+++ /dev/null
@@ -1,252 +0,0 @@
-///
-
-import React from 'react';
-import { CommonProps } from '../util';
-import { PopupProps } from '../overlay';
-import { TreeProps, Node } from '../tree';
-import { item } from '../select';
-
-interface HTMLAttributesWeak extends React.HTMLAttributes {
- defaultValue?: any;
- onChange?: any;
-}
-
-export interface TreeSelectProps extends HTMLAttributesWeak, CommonProps {
- /**
- * 树节点
- */
- children?: React.ReactNode;
-
- /**
- * 选择框大小
- */
- size?: 'small' | 'medium' | 'large';
-
- /**
- * 选择框占位符
- */
- placeholder?: string;
-
- /**
- * 是否禁用
- */
- disabled?: boolean;
-
- /**
- * 是否有下拉箭头
- */
- hasArrow?: boolean;
-
- /**
- * 是否有边框
- */
- hasBorder?: boolean;
-
- /**
- * 是否有清空按钮
- */
- hasClear?: boolean;
-
- /**
- * 自定义内联 label
- */
- label?: React.ReactNode;
-
- /**
- * 是否只读,只读模式下可以展开弹层但不能选择
- */
- readOnly?: boolean;
-
- /**
- * 下拉框是否与选择器对齐
- */
- autoWidth?: boolean;
-
- /**
- * 数据源,该属性优先级高于 children
- */
- dataSource?: Array;
-
- /**
- * (受控)当前值
- */
- value?: string | object | Array;
-
- /**
- * (非受控)默认值
- */
- defaultValue?: string | object | Array;
-
- /**
- * value/defaultValue 在 dataSource 中不存在时,是否展示
- */
- preserveNonExistentValue?: boolean;
-
- /**
- * 选中值改变时触发的回调函数
- */
- onChange?: (value: any | Array, data: any | Array) => void;
-
- /**
- * onChange 返回的 value 使用 dataSource 的对象
- */
- useDetailValue?: boolean;
-
- /**
- * 是否一行显示,仅在 multiple 和 treeCheckable 为 true 时生效
- */
- tagInline?: boolean;
-
- /**
- * 隐藏多余 tag 时显示的内容,在 tagInline 生效时起作用
- * @param {Object[]} selectedValues 当前已选中的元素
- * @param {Object[]} [totalValues] 总待选元素,treeCheckedStrategy = 'parent' 时为 undefined
- */
- maxTagPlaceholder?: (
- selectedValues: any[],
- totalValues?: any[]
- ) => React.ReactNode | HTMLElement;
-
- /**
- * 是否自动清除 searchValue
- */
- autoClearSearch?: boolean;
-
- /**
- * 是否显示搜索框
- */
- showSearch?: boolean;
-
- /**
- * 在搜索框中输入时触发的回调函数
- */
- onSearch?: (keyword: string) => void;
- onSearchClear?: (actionType: string) => void;
- /**
- * 无数据时显示内容
- */
- notFoundContent?: React.ReactNode;
-
- /**
- * 是否支持多选
- */
- multiple?: boolean;
-
- /**
- * 下拉框中的树是否支持勾选节点的复选框
- */
- treeCheckable?: boolean;
-
- /**
- * 下拉框中的树勾选节点复选框是否完全受控(父子节点选中状态不再关联)
- */
- treeCheckStrictly?: boolean;
-
- /**
- * 定义选中时回填的方式
- */
- treeCheckedStrategy?: 'all' | 'parent' | 'child';
-
- /**
- * 下拉框中的树是否默认展开所有节点
- */
- treeDefaultExpandAll?: boolean;
-
- /**
- * 下拉框中的树默认展开节点key的数组
- */
- treeDefaultExpandedKeys?: Array;
-
- /**
- * 下拉框中的树异步加载数据的函数,使用请参考[Tree的异步加载数据Demo](https://fusion.design/pc/component/basic/tree#%E5%BC%82%E6%AD%A5%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE)
- */
- treeLoadData?: (node: React.ReactElement) => void;
-
- /**
- * 透传到 Tree 的属性对象
- */
- treeProps?: TreeProps;
-
- /**
- * 初始下拉框是否显示
- */
- defaultVisible?: boolean;
-
- /**
- * 当前下拉框是否显示
- */
- visible?: boolean;
-
- /**
- * 下拉框显示或关闭时触发事件的回调函数
- */
- onVisibleChange?: (visible: boolean, type: string) => void;
-
- /**
- * 下拉框自定义样式对象
- */
- popupStyle?: React.CSSProperties;
-
- /**
- * 下拉框样式自定义类名
- */
- popupClassName?: string;
-
- /**
- * 下拉框挂载的容器节点
- */
- popupContainer?: string | HTMLElement | ((target: HTMLElement) => HTMLElement);
-
- /**
- * 透传到 Popup 的属性对象
- */
- popupProps?: PopupProps;
- /**
- * 是否跟随滚动
- */
- followTrigger?: boolean;
-
- /**
- * 是否为预览态
- */
- isPreview?: boolean;
-
- /**
- * 预览态模式下渲染的内容
- * @param {Array} value 选择值 { label: , value:}
- */
- renderPreview?: (data: string | Array, props: any | Array) => React.ReactNode;
-
- /**
- * 是否开启虚拟滚动
- */
- useVirtual?: boolean;
-
- /**
- * 是否关闭本地搜索
- */
- filterLocal?: boolean;
-
- immutable?: boolean;
-
- /**
- * 填充到选择框里的值的 key,默认是 value
- */
- fillProps?: string;
-
- /**
- * 点击文本是否可以勾选
- */
- clickToCheck?: boolean;
-
- /**
- * 渲染 Select 区域展现内容的方法
- * @param {Object} item 渲染项
- * @param {Object[]} itemPaths 渲染项在dataSource内的路径
- */
- valueRender?: (item: any, itemPaths: item[]) => React.ReactNode;
-}
-
-export default class TreeSelect extends React.Component {
- static Node: typeof Node;
-}
diff --git a/components/tree-select/index.jsx b/components/tree-select/index.tsx
similarity index 52%
rename from components/tree-select/index.jsx
rename to components/tree-select/index.tsx
index a8af758047..afa174429b 100644
--- a/components/tree-select/index.jsx
+++ b/components/tree-select/index.tsx
@@ -1,8 +1,15 @@
import ConfigProvider from '../config-provider';
+import TreeNode from '../tree/view/tree-node';
+import { assignSubComponent } from '../util/component';
import TreeSelect from './tree-select';
+import type { DeprecatedTreeSelectProps } from './types';
-export default ConfigProvider.config(TreeSelect, {
- transform: /* istanbul ignore next */ (props, deprecated) => {
+export type { TreeSelectProps } from './types';
+
+const WithTreeSelectNode = assignSubComponent(TreeSelect, { Node: TreeNode });
+
+export default ConfigProvider.config(WithTreeSelectNode, {
+ transform: (props, deprecated) => {
if ('shape' in props) {
deprecated('shape', 'hasBorder', 'TreeSelect');
const { shape, ...others } = props;
@@ -11,7 +18,7 @@ export default ConfigProvider.config(TreeSelect, {
if ('container' in props) {
deprecated('container', 'popupContainer', 'TreeSelect');
- const { container, ...others } = props;
+ const { container, ...others } = props as DeprecatedTreeSelectProps;
props = { popupContainer: container, ...others };
}
diff --git a/components/tree-select/mobile/index.jsx b/components/tree-select/mobile/index.tsx
similarity index 77%
rename from components/tree-select/mobile/index.jsx
rename to components/tree-select/mobile/index.tsx
index 51930c8eda..57e9560865 100644
--- a/components/tree-select/mobile/index.jsx
+++ b/components/tree-select/mobile/index.tsx
@@ -1,3 +1,4 @@
+// @ts-expect-error meet-react does not export TreeSelect
import { TreeSelect as MeetTreeSelect } from '@alifd/meet-react';
import NextTreeSelect from '../index';
diff --git a/components/tree-select/style.js b/components/tree-select/style.ts
similarity index 100%
rename from components/tree-select/style.js
rename to components/tree-select/style.ts
diff --git a/components/tree-select/tree-select.jsx b/components/tree-select/tree-select.tsx
similarity index 72%
rename from components/tree-select/tree-select.jsx
rename to components/tree-select/tree-select.tsx
index 4be98bcfdc..b7152acb4f 100644
--- a/components/tree-select/tree-select.jsx
+++ b/components/tree-select/tree-select.tsx
@@ -1,9 +1,9 @@
-import React, { Component, Children, isValidElement, cloneElement } from 'react';
+import React, { type ReactNode, Component, Children, isValidElement, cloneElement } from 'react';
import { polyfill } from 'react-lifecycles-compat';
import PropTypes from 'prop-types';
import classNames from 'classnames';
-import Select from '../select';
-import Tree from '../tree';
+import Select, { type DataSourceItem, type ObjectItem } from '../select';
+import Tree, { type NodeInstance, type TreeProps } from '../tree';
import {
normalizeToArray,
getAllCheckedKeys,
@@ -14,38 +14,48 @@ import {
import { func, obj, KEYCODE, str } from '../util';
import zhCN from '../locale/zh-cn';
import { getValueDataSource, valueToSelectKey } from '../select/util';
+import type {
+ Key,
+ TreeSelectProps,
+ TreeSelectState,
+ KeyEntities,
+ TreeSelectDataItem,
+ DataNode,
+} from './types';
+import type { NodeElement } from '../tree/types';
const noop = () => {};
const { Node: TreeNode } = Tree;
const { bindCtx } = func;
const POS_REGEXP = /^\d+(-\d+){1,}$/;
-const flatDataSource = props => {
- const _k2n = {};
- const _p2n = {};
- const _v2n = {};
+const flatDataSource = (props: TreeSelectProps) => {
+ const _k2n: KeyEntities = {};
+ const _p2n: KeyEntities = {};
+ const _v2n: KeyEntities = {};
if ('dataSource' in props) {
- const loop = (data, prefix = '0') =>
- data.map((item, index) => {
+ const loop = (data: TreeSelectDataItem[], prefix = '0') =>
+ data.map((item: TreeSelectDataItem, index) => {
const { value, children } = item;
const pos = `${prefix}-${index}`;
- const key = item.key || pos;
+ const key = (item.key as Key) || pos;
- const newItem = { ...item, key, pos };
+ const newItem = { ...item, key, pos } as DataNode;
if (children && children.length) {
newItem.children = loop(children, pos);
}
- _k2n[key] = _p2n[pos] = _v2n[value] = newItem;
+ // When null and undefined are used as keys of an object, they will be converted to the string type
+ _k2n[key] = _p2n[pos] = _v2n[value as string] = newItem;
return newItem;
});
- loop(props.dataSource);
+ loop(props.dataSource!);
} else if ('children' in props) {
- const loop = (children, prefix = '0') =>
+ const loop = (children: ReactNode, prefix = '0') =>
Children.map(children, (node, index) => {
- if (!React.isValidElement(node)) {
+ if (!isValidElement(node)) {
return;
}
@@ -66,12 +76,12 @@ const flatDataSource = props => {
return { _k2n, _p2n, _v2n };
};
-const isSearched = (label, searchedValue) => {
+const isSearched = (label: ReactNode, searchedValue: string) => {
let labelString = '';
searchedValue = String(searchedValue);
- const loop = arg => {
+ const loop = (arg: ReactNode) => {
if (isValidElement(arg) && arg.props.children) {
Children.forEach(arg.props.children, loop);
} else {
@@ -90,19 +100,19 @@ const isSearched = (label, searchedValue) => {
return false;
};
-const getSearchKeys = (searchedValue, _k2n, _p2n) => {
- const searchedKeys = [];
- const retainedKeys = [];
+const getSearchKeys = (searchedValue: string, _k2n: KeyEntities, _p2n: KeyEntities) => {
+ const searchedKeys: Key[] = [];
+ const retainedKeys: Key[] = [];
Object.keys(_k2n).forEach(k => {
const { label, pos } = _k2n[k];
if (isSearched(label, searchedValue)) {
searchedKeys.push(k);
- const posArr = pos.split('-');
- posArr.forEach((n, i) => {
+ const posArr = pos!.split('-');
+ posArr.forEach((n: string, i: number) => {
if (i > 0) {
const p = posArr.slice(0, i + 1).join('-');
- const kk = _p2n[p].key;
+ const kk = _p2n[p].key as Key;
if (retainedKeys.indexOf(kk) === -1) {
retainedKeys.push(kk);
}
@@ -117,208 +127,64 @@ const getSearchKeys = (searchedValue, _k2n, _p2n) => {
/**
* TreeSelect
*/
-class TreeSelect extends Component {
+class TreeSelect extends Component {
static propTypes = {
prefix: PropTypes.string,
pure: PropTypes.bool,
locale: PropTypes.object,
className: PropTypes.string,
- /**
- * 树节点
- */
children: PropTypes.node,
- /**
- * 选择框大小
- */
size: PropTypes.oneOf(['small', 'medium', 'large']),
- /**
- * 选择框占位符
- */
placeholder: PropTypes.string,
- /**
- * 是否禁用
- */
disabled: PropTypes.bool,
- /**
- * 是否有下拉箭头
- */
hasArrow: PropTypes.bool,
- /**
- * 是否有边框
- */
hasBorder: PropTypes.bool,
- /**
- * 是否有清空按钮
- */
hasClear: PropTypes.bool,
- /**
- * 自定义内联 label
- */
label: PropTypes.node,
- /**
- * 是否只读,只读模式下可以展开弹层但不能选择
- */
readOnly: PropTypes.bool,
- /**
- * 下拉框是否与选择器对齐
- */
autoWidth: PropTypes.bool,
- /**
- * 数据源,该属性优先级高于 children
- */
dataSource: PropTypes.arrayOf(PropTypes.object),
- /**
- * value/defaultValue 在 dataSource 中不存在时,是否展示
- * @version 1.25
- */
preserveNonExistentValue: PropTypes.bool,
- /**
- * (受控)当前值
- */
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.arrayOf(PropTypes.any)]),
- /**
- * (非受控)默认值
- */
- defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.arrayOf(PropTypes.any)]),
- /**
- * 选中值改变时触发的回调函数
- * @param {String|Array} value 选中的值,单选时返回单个值,多选时返回数组
- * @param {Object|Array} data 选中的数据,包括 value, label, pos, key属性,单选时返回单个值,多选时返回数组,父子节点选中关联时,同时选中,只返回父节点
- */
+ value: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.arrayOf(PropTypes.any),
+ ]),
+ defaultValue: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ PropTypes.arrayOf(PropTypes.any),
+ ]),
onChange: PropTypes.func,
- /**
- * 是否一行显示,仅在 multiple 和 treeCheckable 为 true 时生效
- * @version 1.25
- */
tagInline: PropTypes.bool,
- /**
- * 隐藏多余 tag 时显示的内容,在 tagInline 生效时起作用
- * @param {Object[]} selectedValues 当前已选中的元素
- * @param {Object[]} totalValues 总待选元素
- * @returns {reactNode}
- * @version 1.25
- */
maxTagPlaceholder: PropTypes.func,
- /**
- * 选择时是否自动清空 searchValue
- * @version 1.26
- */
autoClearSearch: PropTypes.bool,
- /**
- * 是否显示搜索框
- */
showSearch: PropTypes.bool,
- /**
- * 是否使用本地过滤,在数据源为远程的时候需要关闭此项
- */
filterLocal: PropTypes.bool,
- /**
- * 在搜索框中输入时触发的回调函数
- * @param {String} keyword 输入的关键字
- */
onSearch: PropTypes.func,
onSearchClear: PropTypes.func,
- /**
- * 无数据时显示内容
- */
notFoundContent: PropTypes.node,
- /**
- * 是否支持多选
- */
multiple: PropTypes.bool,
- /**
- * 下拉框中的树是否支持勾选节点的复选框
- */
treeCheckable: PropTypes.bool,
- /**
- * 下拉框中的树勾选节点复选框是否完全受控(父子节点选中状态不再关联)
- */
treeCheckStrictly: PropTypes.bool,
- /**
- * 定义选中时回填的方式
- * @enumdesc 返回所有选中的节点, 父子节点都选中时只返回父节点, 父子节点都选中时只返回子节点
- */
treeCheckedStrategy: PropTypes.oneOf(['all', 'parent', 'child']),
- /**
- * 下拉框中的树是否默认展开所有节点
- */
treeDefaultExpandAll: PropTypes.bool,
- /**
- * 下拉框中的树默认展开节点key的数组
- */
treeDefaultExpandedKeys: PropTypes.arrayOf(PropTypes.string),
- /**
- * 下拉框中的树异步加载数据的函数,使用请参考[Tree的异步加载数据Demo](https://fusion.design/pc/component/tree?themeid=2#dynamic-container)
- * @param {ReactElement} node 被点击展开的节点
- */
treeLoadData: PropTypes.func,
- /**
- * 透传到 Tree 的属性对象
- */
treeProps: PropTypes.object,
- /**
- * 初始下拉框是否显示
- */
defaultVisible: PropTypes.bool,
- /**
- * 当前下拉框是否显示
- */
visible: PropTypes.bool,
- /**
- * 下拉框显示或关闭时触发事件的回调函数
- * @param {Boolean} visible 是否显示
- * @param {String} type 触发显示关闭的操作类型
- */
onVisibleChange: PropTypes.func,
- /**
- * 下拉框自定义样式对象
- */
popupStyle: PropTypes.object,
- /**
- * 下拉框样式自定义类名
- */
popupClassName: PropTypes.string,
- /**
- * 下拉框挂载的容器节点
- */
popupContainer: PropTypes.any,
- /**
- * 透传到 Popup 的属性对象
- */
popupProps: PropTypes.object,
- /**
- * 是否跟随滚动
- */
followTrigger: PropTypes.bool,
- /**
- * 是否为预览态
- */
isPreview: PropTypes.bool,
- /**
- * 预览态模式下渲染的内容
- * @param {Array} value 选择值 { label: , value:}
- */
renderPreview: PropTypes.func,
- /**
- * 是否开启虚拟滚动
- */
useVirtual: PropTypes.bool,
- /**
- * 是否是不可变数据
- * @version 1.23
- */
immutable: PropTypes.bool,
- /**
- * 点击文本是否可以勾选
- */
clickToCheck: PropTypes.bool,
- /**
- * 渲染 Select 展现内容的方法
- * @param {Object} item 渲染节点的item
- * @param {Object[]} itemPaths item的全路径数组
- * @return {ReactNode} 展现内容
- * @default item => `item.label || item.value`
- */
valueRender: PropTypes.func,
useDetailValue: PropTypes.bool,
};
@@ -360,14 +226,19 @@ class TreeSelect extends Component {
clickToCheck: false,
};
- constructor(props, context) {
+ tree: InstanceType;
+ select: InstanceType;
+
+ constructor(props: TreeSelectProps, context?: unknown) {
super(props, context);
const { defaultVisible, visible, defaultValue, value } = props;
this.state = {
visible: typeof visible === 'undefined' ? defaultVisible : visible,
- value: normalizeToArray(typeof value === 'undefined' ? defaultValue : value),
+ value: normalizeToArray(
+ typeof value === 'undefined' ? defaultValue : value
+ ) as TreeSelectState['value'],
searchedValue: '',
expandedKeys: [],
searchedKeys: [],
@@ -380,7 +251,12 @@ class TreeSelect extends Component {
// init value/mapValueDS when defaultValue is not undefined
if (this.state.value !== undefined) {
- this.state.mapValueDS = getValueDataSource(this.state.value, this.state.mapValueDS).mapValueDS;
+ // @ts-expect-error Since `this.state` cannot be reassigned, use `@ts-expect-error` temporarily
+ this.state.mapValueDS = getValueDataSource(
+ this.state.value,
+ this.state.mapValueDS
+ ).mapValueDS;
+ // @ts-expect-error Since `this.state` cannot be reassigned, use `@ts-expect-error` temporarily
this.state.value = this.state.value.map(v => {
return valueToSelectKey(v);
});
@@ -402,8 +278,8 @@ class TreeSelect extends Component {
]);
}
- static getDerivedStateFromProps(props, state) {
- const st = {};
+ static getDerivedStateFromProps(props: TreeSelectProps, state: TreeSelectState) {
+ const st = {} as Partial;
if ('value' in props) {
const valueArray = normalizeToArray(props.value);
@@ -434,21 +310,21 @@ class TreeSelect extends Component {
};
}
- getKeysByValue(value) {
+ getKeysByValue(value: TreeSelectState['value']) {
return value.reduce((ret, v) => {
- const k = this.state._v2n[v] && this.state._v2n[v].key;
+ const k = this.state._v2n[v as string] && this.state._v2n[v as string].key;
if (k) {
ret.push(k);
}
return ret;
- }, []);
+ }, [] as string[]);
}
- getValueByKeys(keys) {
+ getValueByKeys(keys: Key[]) {
return keys.map(k => this.state._k2n[k].value);
}
- getFullItemPath(item) {
+ getFullItemPath(item: TreeSelectState['_k2n'][Key]) {
if (!item) {
return [];
}
@@ -463,7 +339,7 @@ class TreeSelect extends Component {
return [item];
}
- getValueForSelect(value) {
+ getValueForSelect(value: TreeSelectState['value']) {
const { treeCheckedStrategy } = this.props;
const nonExistentValueKeys = this.getNonExistentValueKeys();
@@ -487,20 +363,21 @@ class TreeSelect extends Component {
return [...values, ...nonExistentValueKeys];
}
- getData(value, forSelect) {
+ getData(value: TreeSelectState['value'], forSelect?: boolean) {
const { preserveNonExistentValue } = this.props;
const { mapValueDS } = this.state;
- const ret = value.reduce((ret, v) => {
+ const ret = (value as DataSourceItem[]).reduce((ret: ObjectItem[], v: Key) => {
const k = this.state._v2n[v] && this.state._v2n[v].key;
if (k) {
- const { label, pos, disabled, checkboxDisabled, children, ...rests } = this.state._k2n[k];
+ const { label, pos, disabled, checkboxDisabled, children, ...rests } =
+ this.state._k2n[k];
const d = {
...rests,
value: v,
label,
pos,
- };
+ } as ObjectItem;
if (forSelect) {
d.disabled = disabled || checkboxDisabled;
} else {
@@ -527,48 +404,52 @@ class TreeSelect extends Component {
if (!preserveNonExistentValue) {
return [];
}
- const nonExistentValues = value.filter(v => !this.state._v2n[v]);
+ const nonExistentValues = value.filter((v: Key) => !this.state._v2n[v]);
return nonExistentValues;
}
getNonExistentValueKeys() {
const nonExistentValues = this.getNonExistentValues();
return nonExistentValues.map(v => {
- if (typeof v === 'object' && v.hasOwnProperty('value')) {
- return v.value;
+ if (typeof v === 'object' && v!.hasOwnProperty('value')) {
+ // @ts-expect-error v must not be object
+ return v!.value;
}
return v;
});
}
- saveTreeRef(ref) {
+ saveTreeRef(ref: InstanceType) {
this.tree = ref;
}
- saveSelectRef(ref) {
+ saveSelectRef(ref: InstanceType) {
this.select = ref;
}
- handleVisibleChange(visible, type) {
+ handleVisibleChange(visible: boolean, type?: string) {
if (!('visible' in this.props)) {
this.setState({
visible,
});
}
- if (['fromTree', 'keyboard'].indexOf(type) !== -1 && !visible) {
+ if (['fromTree', 'keyboard'].indexOf(type!) !== -1 && !visible) {
this.select.focusInput();
}
- this.props.onVisibleChange(visible, type);
+ this.props.onVisibleChange!(visible, type!);
}
- triggerOnChange(value, data) {
+ triggerOnChange(
+ value: ObjectItem[] | DataSourceItem[] | ObjectItem['value'] | null,
+ data: ObjectItem[] | ObjectItem | null
+ ) {
const { useDetailValue, onChange } = this.props;
- onChange(useDetailValue ? data : value, data);
+ onChange!(useDetailValue ? data : value, data);
}
- handleSelect(selectedKeys, extra) {
+ handleSelect(selectedKeys: Key[], extra: { selected: boolean }) {
const { multiple, autoClearSearch } = this.props;
const { selected } = extra;
@@ -589,7 +470,9 @@ class TreeSelect extends Component {
const data = this.getData(value);
const selectedData = this.getData(selectedValue);
// 单选情况下,不返回 nonExistentValue,直接返回当前选择值,避免无法改选的问题
- multiple ? this.triggerOnChange(value, data) : this.triggerOnChange(selectedValue[0], selectedData[0]);
+ multiple
+ ? this.triggerOnChange(value, data)
+ : this.triggerOnChange(selectedValue[0], selectedData[0]);
// clear search value manually
if (autoClearSearch) {
@@ -600,7 +483,7 @@ class TreeSelect extends Component {
}
}
- handleCheck(checkedKeys) {
+ handleCheck(checkedKeys: Key[]) {
const { autoClearSearch } = this.props;
let value = this.getValueByKeys(checkedKeys);
@@ -621,7 +504,7 @@ class TreeSelect extends Component {
}
}
- handleRemove(removedItem) {
+ handleRemove(removedItem: ObjectItem) {
const { value: removedValue } = removedItem;
const { treeCheckable, treeCheckStrictly, treeCheckedStrategy } = this.props;
@@ -630,17 +513,17 @@ class TreeSelect extends Component {
// there's linkage relationship among nodes
treeCheckable &&
!treeCheckStrictly &&
- ['parent', 'all'].indexOf(treeCheckedStrategy) !== -1 &&
+ ['parent', 'all'].indexOf(treeCheckedStrategy!) !== -1 &&
// value exits in datasource
- this.state._v2n[removedValue]
+ this.state._v2n[removedValue as string]
) {
- const removedPos = this.state._v2n[removedValue].pos;
- value = this.state.value.filter(v => {
+ const removedPos = this.state._v2n[removedValue as string].pos;
+ value = this.state.value.filter((v: string) => {
const p = this.state._v2n[v].pos;
- return !isDescendantOrSelf(removedPos, p);
+ return !isDescendantOrSelf(removedPos!, p!);
});
- const nums = removedPos.split('-');
+ const nums = removedPos!.split('-');
for (let i = nums.length; i > 2; i--) {
const parentPos = nums.slice(0, i - 1).join('-');
const parentValue = this.state._p2n[parentPos].value;
@@ -665,7 +548,7 @@ class TreeSelect extends Component {
this.triggerOnChange(value, data);
}
- handleSearch(searchedValue) {
+ handleSearch(searchedValue: string) {
const { _k2n, _p2n } = this.state;
const { searchedKeys, retainedKeys } = getSearchKeys(searchedValue, _k2n, _p2n);
@@ -677,25 +560,25 @@ class TreeSelect extends Component {
autoExpandParent: true,
});
- this.props.onSearch(searchedValue);
+ this.props.onSearch!(searchedValue);
}
- handleSearchClear(triggerType) {
+ handleSearchClear(triggerType: string) {
this.setState({
searchedValue: '',
expandedKeys: [],
});
- this.props.onSearchClear(triggerType);
+ this.props.onSearchClear!(triggerType);
}
- handleExpand(expandedKeys) {
+ handleExpand(expandedKeys: Key[]) {
this.setState({
expandedKeys,
autoExpandParent: false,
});
}
- handleKeyDown(e) {
+ handleKeyDown(e: React.KeyboardEvent) {
const { onKeyDown } = this.props;
const { visible } = this.state;
@@ -718,7 +601,7 @@ class TreeSelect extends Component {
}
}
- handleChange(value, triggerType) {
+ handleChange(value: DataSourceItem[] | DataSourceItem, triggerType: string) {
if (this.props.hasClear && triggerType === 'clear') {
if (!('value' in this.props)) {
this.setState({
@@ -730,15 +613,15 @@ class TreeSelect extends Component {
}
}
- searchNodes(children) {
+ searchNodes(children: ReactNode) {
const { searchedKeys, retainedKeys } = this.state;
- const loop = children => {
- const retainedNodes = [];
- Children.forEach(children, child => {
- if (searchedKeys.indexOf(child.key) > -1) {
+ const loop = (children: ReactNode) => {
+ const retainedNodes: NodeElement[] = [];
+ Children.forEach(children, (child: NodeElement) => {
+ if (searchedKeys.indexOf(child.key!) > -1) {
retainedNodes.push(child);
- } else if (retainedKeys.indexOf(child.key) > -1) {
+ } else if (retainedKeys.indexOf(child.key!) > -1) {
const retainedNode = child.props.children
? cloneElement(child, {}, loop(child.props.children))
: child;
@@ -756,32 +639,38 @@ class TreeSelect extends Component {
return loop(children);
}
- createNodesByData(data, searching) {
+ createNodesByData(data: TreeSelectProps['dataSource'], searching?: boolean) {
const { searchedKeys, retainedKeys } = this.state;
- const loop = (data, isParentMatched, prefix = '0') => {
- const retainedNodes = [];
+ const loop = (
+ data: TreeSelectProps['dataSource'],
+ isParentMatched?: boolean,
+ prefix = '0'
+ ) => {
+ const retainedNodes: NodeElement[] = [];
- data.forEach((item, index) => {
- const { children, ...others } = item;
+ data!.forEach((item, index) => {
+ const { children, ...others } = item as TreeSelectDataItem;
const pos = `${prefix}-${index}`;
const key = this.state._p2n[pos].key;
- const addNode = (isParentMatched, hide) => {
+ const addNode = (isParentMatched?: boolean, hide?: boolean) => {
if (hide) {
others.style = { display: 'none' };
}
retainedNodes.push(
- {children && children.length ? loop(children, isParentMatched, pos) : null}
+ {children && children.length
+ ? loop(children, isParentMatched, pos)
+ : null}
);
};
if (searching) {
- if (searchedKeys.indexOf(key) > -1 || isParentMatched) {
+ if (searchedKeys.indexOf(key!) > -1 || isParentMatched) {
addNode(true);
- } else if (retainedKeys.indexOf(key) > -1) {
+ } else if (retainedKeys.indexOf(key!) > -1) {
addNode(false);
} else {
addNode(false, true);
@@ -836,7 +725,7 @@ class TreeSelect extends Component {
useVirtual,
isNodeBlock: true,
clickToCheck,
- };
+ } as TreeProps;
// 使用虚拟滚动 设置默认高度
if (useVirtual) {
@@ -861,7 +750,7 @@ class TreeSelect extends Component {
} else {
treeProps.selectedKeys = keys;
if (!readOnly) {
- treeProps.onSelect = this.handleSelect;
+ treeProps.onSelect = this.handleSelect as TreeProps['onSelect'];
}
}
@@ -871,12 +760,14 @@ class TreeSelect extends Component {
treeProps.expandedKeys = expandedKeys;
treeProps.autoExpandParent = autoExpandParent;
treeProps.onExpand = this.handleExpand;
- treeProps.filterTreeNode = node => {
- return searchedKeys.indexOf(node.props.eventKey) > -1;
+ treeProps.filterTreeNode = (node: NodeInstance) => {
+ return searchedKeys.indexOf(node.props.eventKey!) > -1;
};
if (searchedKeys.length) {
- newChildren = dataSource ? this.createNodesByData(dataSource, true) : this.searchNodes(children);
+ newChildren = dataSource
+ ? this.createNodesByData(dataSource, true)
+ : this.searchNodes(children);
} else {
notFound = true;
}
@@ -902,7 +793,9 @@ class TreeSelect extends Component {
return (
{notFound ? (
-
{notFoundContent}
+
+ {notFoundContent}
+
) : (
{newChildren}
@@ -912,7 +805,10 @@ class TreeSelect extends Component {
);
}
- renderPreview(data, others) {
+ renderPreview(
+ data: ObjectItem[] | ObjectItem,
+ others: Omit
+ ) {
const { prefix, className, renderPreview } = this.props;
const previewCls = classNames(className, `${prefix}form-preview`);
@@ -944,12 +840,12 @@ class TreeSelect extends Component {
* treeCheckedStrategy = 'parent': totalValue 无意义,不返回
* treeCheckedStrategy = 'child': totalValue 为所有 leaf 节点
*/
- renderMaxTagPlaceholder(value, totalValue) {
+ renderMaxTagPlaceholder(value: ObjectItem[], totalValue: ObjectItem[]) {
// 这里的 totalValue 为所有 leaf 节点
const { treeCheckStrictly, maxTagPlaceholder, treeCheckedStrategy, locale } = this.props;
const { _v2n } = this.state;
- let treeSelectTotalValue = totalValue; // all the leaf nodes
+ let treeSelectTotalValue: ObjectItem[] | undefined = totalValue; // all the leaf nodes
// calculate total value
if (treeCheckStrictly) {
@@ -971,17 +867,16 @@ class TreeSelect extends Component {
// default render function
if (treeCheckedStrategy === 'parent') {
// don't show totalValue when treeCheckedStrategy = 'parent'
- return `${str.template(locale.shortMaxTagPlaceholder, {
+ return `${str.template(locale!.shortMaxTagPlaceholder, {
selected: value.length,
})}`;
}
- return `${str.template(locale.maxTagPlaceholder, {
+ return `${str.template(locale!.maxTagPlaceholder, {
selected: value.length,
- total: treeSelectTotalValue.length,
+ total: treeSelectTotalValue!.length,
})}`;
}
- /*eslint-enable*/
render() {
const {
prefix,
@@ -1015,16 +910,17 @@ class TreeSelect extends Component {
const valueRenderProps =
typeof valueRender === 'function'
? {
- valueRender: item => {
+ valueRender: (item: TreeSelectState['_k2n'][Key]) => {
return valueRender(item, this.getFullItemPath(item));
},
}
: undefined;
// if (non-leaf 节点可选 & 父子节点选中状态需要联动),需要额外计算父子节点间的联动关系
- const valueForSelect = treeCheckable && !treeCheckStrictly ? this.getValueForSelect(value) : value;
+ const valueForSelect =
+ treeCheckable && !treeCheckStrictly ? this.getValueForSelect(value) : value;
- let data = this.getData(valueForSelect, true);
+ let data: ObjectItem[] | ObjectItem = this.getData(valueForSelect, true);
if (!multiple && !treeCheckable) {
data = data[0];
}
@@ -1073,6 +969,4 @@ class TreeSelect extends Component {
}
}
-TreeSelect.Node = TreeNode;
-
export default polyfill(TreeSelect);
diff --git a/components/tree-select/types.ts b/components/tree-select/types.ts
new file mode 100644
index 0000000000..c2d71bd79a
--- /dev/null
+++ b/components/tree-select/types.ts
@@ -0,0 +1,375 @@
+import type React from 'react';
+import type { CommonProps } from '../util';
+import type { PopupProps } from '../overlay';
+import type { TreeProps } from '../tree';
+import type { ObjectItem } from '../select';
+import type { DataSourceItem, BaseProps as SelectProps } from '../select/types';
+import type { BasicDataNode } from '../tree/types';
+
+export type Key = string;
+export interface DataNode extends ObjectItem, BasicDataNode {
+ key: Key;
+ pos: string;
+ children?: DataNode[];
+}
+export interface TreeSelectDataItem extends ObjectItem, BasicDataNode {
+ children?: TreeSelectDataItem[];
+}
+
+export type KeyEntities = Record;
+
+type HTMLAttributesWeak = Omit, 'defaultValue' | 'onChange'>;
+
+/**
+ * @api TreeSelect
+ */
+export interface TreeSelectProps extends HTMLAttributesWeak, CommonProps {
+ /**
+ * 树节点
+ * @en Tree node
+ */
+ children?: React.ReactNode;
+
+ /**
+ * 选择框大小
+ * @en Select size
+ * @defaultValue 'medium'
+ */
+ size?: 'small' | 'medium' | 'large';
+
+ /**
+ * 选择框占位符
+ * @en Select placeholder
+ */
+ placeholder?: string;
+
+ /**
+ * 是否禁用
+ * @en Whether to be disabled
+ * @defaultValue false
+ */
+ disabled?: boolean;
+
+ /**
+ * 是否有下拉箭头
+ * @en Whether to show the arrow
+ * @defaultValue true
+ */
+ hasArrow?: boolean;
+
+ /**
+ * 是否有边框
+ * @en Whether to show the border
+ * @defaultValue true
+ */
+ hasBorder?: boolean;
+
+ /**
+ * 是否有清空按钮
+ * @en Whether to show the clear button
+ * @defaultValue true
+ */
+ hasClear?: boolean;
+
+ /**
+ * 自定义内联 label
+ * @en Custom inline label
+ */
+ label?: React.ReactNode;
+
+ /**
+ * 是否只读,只读模式下可以展开弹层但不能选择
+ * @en Whether to be read-only (read-only mode can expand the popup but cannot select)
+ */
+ readOnly?: boolean;
+
+ /**
+ * 下拉框是否与选择器对齐
+ * @en Whether the drop-down box is aligned with the selector
+ * @defaultValue true
+ */
+ autoWidth?: boolean;
+
+ /**
+ * 数据源,该属性优先级高于 children
+ * @en Data source (higher priority than children)
+ */
+ dataSource?: TreeSelectDataItem[];
+
+ /**
+ * (受控)当前值
+ * @en Current value (Controlled)
+ */
+ value?: DataSourceItem[] | DataSourceItem;
+
+ /**
+ * (非受控)默认值
+ * @en Default value (Uncontrolled)
+ * @defaultValue null
+ */
+ defaultValue?: SelectProps['defaultValue'];
+
+ /**
+ * value/defaultValue 在 dataSource 中不存在时,是否展示
+ * @en Whether to display when value/defaultValue does not exist in dataSource
+ * @defaultValue false
+ * @version 1.25
+ */
+ preserveNonExistentValue?: boolean;
+
+ /**
+ * 选中值改变时触发的回调函数
+ * @en Callback when the selected value changes
+ * @defaultValue () =\> \{\}
+ */
+ onChange?: (
+ value: DataSourceItem[] | DataSourceItem,
+ data: ObjectItem[] | ObjectItem | null
+ ) => void;
+
+ /**
+ * onChange 返回的 value 使用 dataSource 的对象
+ * @en onChange returns the value using the object in dataSource
+ * @skip
+ */
+ useDetailValue?: boolean;
+
+ /**
+ * 是否一行显示,仅在 multiple 和 treeCheckable 为 true 时生效
+ * @en Whether to display on one line (only effective when multiple and treeCheckable are true)
+ * @defaultValue false
+ * @version 1.25
+ */
+ tagInline?: boolean;
+
+ /**
+ * 隐藏多余 tag 时显示的内容,在 tagInline 生效时起作用
+ * @en Content to display when hiding excess tags (effective when tagInline is true)
+ * @param selectedValues - 当前已选中的元素 - Selected element
+ * @param totalValues - 总待选元素,treeCheckedStrategy = 'parent' 时为 undefined - Total pending element, treeCheckedStrategy = 'parent' is undefined
+ * @returns ReactNode | HTMLElement - ReactNode or HTMLElement
+ * @version 1.25
+ */
+ maxTagPlaceholder?: (
+ selectedValues: ObjectItem[],
+ totalValues?: ObjectItem[]
+ ) => React.ReactNode | HTMLElement;
+
+ /**
+ * 是否自动清除 searchValue
+ * @en Whether to automatically clear searchValue
+ * @defaultValue true
+ * @version 1.26
+ */
+ autoClearSearch?: boolean;
+
+ /**
+ * 是否显示搜索框
+ * @en Whether to show the search box
+ * @defaultValue false
+ */
+ showSearch?: boolean;
+
+ /**
+ * 在搜索框中输入时触发的回调函数
+ * @en Callback when input in search box changes
+ * @defaultValue () =\> \{\}
+ */
+ onSearch?: (keyword: string) => void;
+ /**
+ * onSearchClear
+ * @skip
+ */
+ onSearchClear?: (actionType: string) => void;
+
+ /**
+ * 无数据时显示内容
+ * @en Content to display when there is no data
+ * @defaultValue 'Not Found'
+ */
+ notFoundContent?: React.ReactNode;
+
+ /**
+ * 是否支持多选
+ * @en Whether to support multiple selection
+ * @defaultValue false
+ */
+ multiple?: boolean;
+
+ /**
+ * 下拉框中的树是否支持勾选节点的复选框
+ * @en Whether the check box of the tree in the drop-down box supports the selection of the child node checkbox
+ * @defaultValue false
+ */
+ treeCheckable?: boolean;
+
+ /**
+ * 下拉框中的树勾选节点复选框是否完全受控(父子节点选中状态不再关联)
+ * @en Whether the check box of the tree in the drop-down box is completely controlled (the parent-child node selected status is no longer associated)
+ * @defaultValue false
+ */
+ treeCheckStrictly?: boolean;
+
+ /**
+ * 定义选中时回填的方式
+ * @en Definition of how to fill in when selected
+ * @defaultValue 'parent'
+ */
+ treeCheckedStrategy?: 'all' | 'parent' | 'child';
+
+ /**
+ * 下拉框中的树是否默认展开所有节点
+ * @en Whether the tree in the drop-down box is expanded by default all nodes
+ * @defaultValue false
+ */
+ treeDefaultExpandAll?: boolean;
+
+ /**
+ * 下拉框中的树默认展开节点key的数组
+ * @en The array of keys of the nodes expanded by default in the tree in the drop-down box
+ * @defaultValue []
+ */
+ treeDefaultExpandedKeys?: Array;
+
+ /**
+ * 下拉框中的树异步加载数据的函数,使用请参考[Tree的异步加载数据Demo](https://fusion.design/pc/component/basic/tree#%E5%BC%82%E6%AD%A5%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE)
+ * @en The function of asynchronous loading data in the tree in the drop-down box, please refer to [Tree asynchronous loading data Demo](https://fusion.design/pc/component/basic/tree#%E5%BC%82%E6%AD%A5%E5%8A%A0%E8%BD%BD%E6%95%B0%E6%8D%AE)
+ */
+ treeLoadData?: TreeProps['loadData'];
+
+ /**
+ * 透传到 Tree 的属性对象
+ * @en Pass-through to the property object of Tree
+ * @defaultValue \{\}
+ */
+ treeProps?: TreeProps;
+
+ /**
+ * 初始下拉框是否显示
+ * @en Initial display state of the drop-down box
+ * @defaultValue false
+ */
+ defaultVisible?: boolean;
+
+ /**
+ * 当前下拉框是否显示
+ * @en Current display state of the drop-down box (Controlled)
+ */
+ visible?: boolean;
+
+ /**
+ * 下拉框显示或关闭时触发事件的回调函数
+ * @en Callback when the drop-down box is displayed or closed
+ * @defaultValue () =\> \{\}
+ */
+ onVisibleChange?: (visible: boolean, type: string) => void;
+
+ /**
+ * 下拉框自定义样式对象
+ * @en Custom style object for the drop-down box
+ */
+ popupStyle?: React.CSSProperties;
+
+ /**
+ * 下拉框样式自定义类名
+ * @en Custom class name for the drop-down box
+ */
+ popupClassName?: string;
+
+ /**
+ * 下拉框挂载的容器节点
+ * @en Mounting container node for the drop-down box
+ */
+ popupContainer?: string | HTMLElement | ((target: HTMLElement) => HTMLElement);
+
+ /**
+ * 透传到 Popup 的属性对象
+ * @en Pass-through to the property object of Popup
+ */
+ popupProps?: PopupProps;
+
+ /**
+ * 是否跟随滚动
+ * @en Whether to follow scrolling
+ */
+ followTrigger?: boolean;
+
+ /**
+ * 是否为预览态
+ * @en Whether it is in preview mode
+ */
+ isPreview?: boolean;
+
+ /**
+ * 预览态模式下渲染的内容
+ * @en Content rendered in preview mode
+ */
+ renderPreview?: (data: ObjectItem[], props: TreeSelectProps) => React.ReactNode;
+
+ /**
+ * 是否开启虚拟滚动
+ * @en Whether to open virtual scrolling
+ * @defaultValue false
+ */
+ useVirtual?: boolean;
+
+ /**
+ * 是否关闭本地搜索
+ * @en Whether to close local search
+ * @defaultValue true
+ */
+ filterLocal?: boolean;
+
+ /**
+ * 是否是不可变数据
+ * @en Whether it is immutable data
+ * @version 1.23
+ */
+ immutable?: boolean;
+
+ /**
+ * 填充到选择框里的值的 key,默认是 value
+ * @en The key of the value filled in to the Select box, the default is value
+ * @skip
+ */
+ fillProps?: string;
+
+ /**
+ * 点击文本是否可以勾选
+ * @en Whether clicking on the text can be selected
+ * @defaultValue false
+ */
+ clickToCheck?: boolean;
+
+ /**
+ * 渲染 Select 区域展现内容的方法
+ * @en Method for rendering Select area display content
+ * @defaultValue (item) =\> item.label || item.value
+ * @param item - 渲染项 - Extra item
+ * @param itemPaths - 渲染项在dataSource内的路径 - Extra item path
+ * @returns ReactNode - 展现内容 - Display content
+ */
+ valueRender?: (item: TreeSelectState['_k2n'][Key], itemPaths: ObjectItem[]) => React.ReactNode;
+}
+
+export interface TreeSelectState {
+ visible: TreeSelectProps['visible'];
+ value: ObjectItem['value'][];
+ searchedValue: string;
+ expandedKeys: Key[];
+ searchedKeys: Key[];
+ retainedKeys: Key[];
+ autoExpandParent: boolean;
+ mapValueDS: Record;
+ _k2n: KeyEntities;
+ _p2n: KeyEntities;
+ _v2n: KeyEntities;
+}
+
+export interface DeprecatedTreeSelectProps extends TreeSelectProps {
+ /**
+ * Use popupContainer instead
+ * @deprecated Use popupContainer instead
+ */
+ container?: TreeSelectProps['popupContainer'];
+}
diff --git a/components/tree/__docs__/index.en-us.md b/components/tree/__docs__/index.en-us.md
index 9136ce8dd7..472cef72b1 100644
--- a/components/tree/__docs__/index.en-us.md
+++ b/components/tree/__docs__/index.en-us.md
@@ -17,48 +17,48 @@ Folders, organizational structures, taxonomy, countries, regions, etc. Most of t
### Tree
-| Param | Description | Type | Default Value | Required | Supported Version |
-| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------- | ----------------- |
-| children | Chidren of the tree component | React.ReactNode | - | | - |
-| dataSource | Data source of the tree component | TreeDataType[] | - | | - |
-| showLine | Show line or not | boolean | false | yes | - |
-| selectable | Selectable nodes or not | boolean | false | yes | - |
-| selectedKeys | Selected keys array (controlled) | string[] | - | | - |
-| defaultSelectedKeys | Default selected keys array | string[] | [] | | - |
-| onSelect | Callback function when selected or unselected a node
**signature**:
**params**:
_selectedKeys_: Array of selected keys
_extra_: Extra parameters
**return**:
Void | (
selectedKeys: string[],
extra: {
selectedNodes: Array;
node: NodeInstance;
selected: boolean;
event: React.KeyboardEvent;
}
) => void | () =\> \{\} | | - |
-| multiple | Multiple selectable | boolean | false | | - |
-| checkable | Checkable nodes or not | boolean | false | | - |
-| checkedKeys | Checked keys array or `{checked: Array, indeterminate: Array}` object (controlled) | \| {
checked: string[];
indeterminate: string[];
}
\| string[] | - | | - |
-| defaultCheckedKeys | Default checked keys array (uncontrolled) | \| {
checked: string[];
indeterminate: string[];
}
\| string[] | [] | yes | - |
-| checkStrictly | Whether the checkbox is fully controlled (parent | boolean | false | yes | - |
-| checkedStrategy | Define how to fill the checked state when selected | 'all' \| 'parent' \| 'child' | 'all' | yes | - |
-| onCheck | Callback function when checked or unchecked a checkbox
**signature**:
**params**:
_checkedKeys_: Array of checked keys
_extra_: Extra parameters
**return**:
Void | (
checkedKeys: string[],
extra: {
checkedNodes: Array;
checkedNodesPositions: Array;
indeterminateKeys: string[];
node: NodeInstance;
checked: boolean;
key: Key;
}
) => void | () =\> \{\} | yes | - |
-| expandedKeys | Expanded keys array (controlled) | string[] | - | | - |
-| defaultExpandedKeys | Default expanded keys array (uncontrolled) | string[] | [] | yes | - |
-| defaultExpandAll | Whether to expand all nodes by default | boolean | false | yes | - |
-| autoExpandParent | Whether to automatically expand parent nodes | boolean | true | yes | - |
-| onExpand | Callback function when expanded or collapsed a node
**signature**:
**params**:
_expandedKeys_: Array of expanded keys
_extra_: Extra parameters
**return**:
Void | (
expandedKeys: string[],
extra: {
node: NodeInstance;
expanded: boolean;
}
) => void | () =\> \{\} | yes | - |
-| editable | Editable nodes or not | boolean | false | yes | - |
-| onEditFinish | Callback function when edit finish
**signature**:
**params**:
_key_: Node key
_label_: Node label
_node_: Node instance
**return**:
Void | (key: Key, label: string, node: NodeInstance) => void | () =\> \{\} | yes | - |
-| draggable | Draggable nodes or not | boolean | false | yes | - |
-| onDragStart | Callback function when drag start
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: {
event: React.MouseEvent;
node: NodeInstance;
expandedKeys: string[];
}) => void | () =\> \{\} | yes | - |
-| onDragEnter | Callback function when drag enter
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: {
event: React.MouseEvent;
node: NodeInstance;
expandedKeys: Array;
}) => void | () =\> \{\} | yes | - |
-| onDragOver | Callback function when drag over
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | yes | - |
-| onDragLeave | Callback function when drag leave
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | yes | - |
-| onDragEnd | Callback function when drag end
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | yes | - |
-| onDrop | Callback function when drop
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: {
event: React.MouseEvent;
node: NodeInstance;
dragNode: NodeInstance;
dragNodesKeys: Array;
dropPosition: number;
}) => void | () =\> \{\} | yes | - |
-| canDrop | Whether the node can be a target node for drag
**signature**:
**params**:
_info_: Extra parameters
**return**:
Whether can be a target node | (info: {
event?: React.MouseEvent;
node: NodeInstance;
dragNode: NodeInstance;
dragNodesKeys: Array;
dropPosition: number;
}) => boolean | () =\> \{\} | yes | - |
-| loadData | Asynchronous load data function
**signature**:
**params**:
_node_: Node instance | (node: NodeInstance) => Promise | - | | - |
-| filterTreeNode | Highlight the node
**signature**:
**params**:
_node_: Node instance
**return**:
Whether is filtered | (node: NodeInstance) => boolean | - | | - |
-| onRightClick | Callback function when right click a node
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | yes | - |
-| isLabelBlock | Whether the node occupies the remaining space | boolean | false | | - |
-| isNodeBlock | Whether the node occupies a line | boolean \| Record | false | | - |
-| animation | Whether to open the animation | boolean | true | | - |
-| focusedKey | Current focused submenu or menu item key | Key | - | | - |
-| renderChildNodes | Render child nodes
**signature**:
**params**:
_nodes_: All child nodes
**return**:
Child nodes | (nodes: NodeElement[]) => React.ReactNode | - | | - |
-| labelRender | Render a single child node
**signature**:
**params**:
_node_: Node data
**return**:
Return node | (node: Record) => React.ReactNode | - | | 1.25 |
-| immutable | Whether it is immutable data | boolean | false | | 1.23 |
-| useVirtual | Whether to open virtual scrolling | boolean | false | | - |
+| Param | Description | Type | Default Value | Required | Supported Version |
+| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------- | ----------------- |
+| children | Chidren of the tree component | React.ReactNode | - | | - |
+| dataSource | Data source of the tree component | TreeDataType[] | - | | - |
+| showLine | Show line or not | boolean | false | | - |
+| selectable | Selectable nodes or not | boolean | false | | - |
+| selectedKeys | Selected keys array (controlled) | string[] | - | | - |
+| defaultSelectedKeys | Default selected keys array | string[] | [] | | - |
+| onSelect | Callback function when selected or unselected a node
**signature**:
**params**:
_selectedKeys_: Array of selected keys
_extra_: Extra parameters
**return**:
Void | (
selectedKeys: string[],
extra: {
selectedNodes: Array\;
node: NodeInstance;
selected: boolean;
event: React.KeyboardEvent \| React.MouseEvent;
}
) => void | () =\> \{\} | | - |
+| multiple | Multiple selectable | boolean | false | | - |
+| checkable | Checkable nodes or not | boolean | false | | - |
+| checkedKeys | Checked keys array or `{checked: Array, indeterminate: Array}` object (controlled) | \| {
checked: string[];
indeterminate: string[];
}
\| string[] | - | | - |
+| defaultCheckedKeys | Default checked keys array (uncontrolled) | \| {
checked: string[];
indeterminate: string[];
}
\| string[] | [] | | - |
+| checkStrictly | Whether the checkbox is fully controlled (parent | boolean | false | | - |
+| checkedStrategy | Define how to fill the checked state when selected | 'all' \| 'parent' \| 'child' | 'all' | | - |
+| onCheck | Callback function when checked or unchecked a checkbox
**signature**:
**params**:
_checkedKeys_: Array of checked keys
_extra_: Extra parameters
**return**:
Void | (
checkedKeys: string[],
extra: {
checkedNodes: Array\;
checkedNodesPositions: Array\;
indeterminateKeys: string[];
node: NodeInstance;
checked: boolean;
key: Key;
}
) => void | () =\> \{\} | | - |
+| expandedKeys | Expanded keys array (controlled) | string[] | - | | - |
+| defaultExpandedKeys | Default expanded keys array (uncontrolled) | string[] | [] | | - |
+| defaultExpandAll | Whether to expand all nodes by default | boolean | false | | - |
+| autoExpandParent | Whether to automatically expand parent nodes | boolean | true | | - |
+| onExpand | Callback function when expanded or collapsed a node
**signature**:
**params**:
_expandedKeys_: Array of expanded keys
_extra_: Extra parameters
**return**:
Void | (
expandedKeys: string[],
extra: {
node: NodeInstance;
expanded: boolean;
}
) => void | () =\> \{\} | | - |
+| editable | Editable nodes or not | boolean | false | | - |
+| onEditFinish | Callback function when edit finish
**signature**:
**params**:
_key_: Node key
_label_: Node label
_node_: Node instance
**return**:
Void | (key: Key, label: string, node: NodeInstance) => void | () =\> \{\} | | - |
+| draggable | Draggable nodes or not | boolean | false | | - |
+| onDragStart | Callback function when drag start
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: {
event: React.MouseEvent;
node: NodeInstance;
expandedKeys: string[];
}) => void | () =\> \{\} | | - |
+| onDragEnter | Callback function when drag enter
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: {
event: React.MouseEvent;
node: NodeInstance;
expandedKeys: Array\;
}) => void | () =\> \{\} | | - |
+| onDragOver | Callback function when drag over
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | | - |
+| onDragLeave | Callback function when drag leave
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | | - |
+| onDragEnd | Callback function when drag end
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | | - |
+| onDrop | Callback function when drop
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: {
event: React.MouseEvent;
node: NodeInstance;
dragNode: NodeInstance;
dragNodesKeys: Array\;
dropPosition: number;
}) => void | () =\> \{\} | | - |
+| canDrop | Whether the node can be a target node for drag
**signature**:
**params**:
_info_: Extra parameters
**return**:
Whether can be a target node | (info: {
event?: React.MouseEvent;
node: NodeInstance;
dragNode: NodeInstance;
dragNodesKeys: Array\;
dropPosition: number;
}) => boolean | () =\> \{\} | | - |
+| loadData | Asynchronous load data function
**signature**:
**params**:
_node_: Node instance | (node: NodeInstance) => Promise\ | - | | - |
+| filterTreeNode | Highlight the node
**signature**:
**params**:
_node_: Node instance
**return**:
Whether is filtered | (node: NodeInstance) => boolean | - | | - |
+| onRightClick | Callback function when right click a node
**signature**:
**params**:
_info_: Extra parameters
**return**:
Void | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | | - |
+| isLabelBlock | Whether the node occupies the remaining space | boolean | false | | - |
+| isNodeBlock | Whether the node occupies a line | boolean \| Record\ | false | | - |
+| animation | Whether to open the animation | boolean | true | | - |
+| focusedKey | Current focused submenu or menu item key | Key | - | | - |
+| renderChildNodes | Render child nodes
**signature**:
**params**:
_nodes_: All child nodes
**return**:
Child nodes | (nodes: NodeElement[]) => React.ReactNode | - | | - |
+| labelRender | Render a single child node
**signature**:
**params**:
_node_: Node data
**return**:
Return node | (node: Record\) => React.ReactNode | - | | 1.25 |
+| immutable | Whether it is immutable data | boolean | false | | 1.23 |
+| useVirtual | Whether to open virtual scrolling | boolean | false | | - |
### Tree.Node
diff --git a/components/tree/__docs__/index.md b/components/tree/__docs__/index.md
index c894046b5b..107bd72c8d 100644
--- a/components/tree/__docs__/index.md
+++ b/components/tree/__docs__/index.md
@@ -17,48 +17,48 @@
### Tree
-| 参数 | 说明 | 类型 | 默认值 | 是否必填 | 支持版本 |
-| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- | -------- |
-| children | 树节点 | React.ReactNode | - | | - |
-| dataSource | 数据源,该属性优先级高于 children | TreeDataType[] | - | | - |
-| showLine | 是否显示树的线 | boolean | false | 是 | - |
-| selectable | 是否支持选中节点 | boolean | false | 是 | - |
-| selectedKeys | (用于受控)当前选中节点 key 的数组 | string[] | - | | - |
-| defaultSelectedKeys | (用于非受控)默认选中节点 key 的数组 | string[] | [] | | - |
-| onSelect | 选中或取消选中节点时触发的回调函数
**签名**:
**参数**:
_selectedKeys_: 选中节点key的数组
_extra_: 额外参数
**返回值**:
空 | (
selectedKeys: string[],
extra: {
selectedNodes: Array;
node: NodeInstance;
selected: boolean;
event: React.KeyboardEvent;
}
) => void | () =\> \{\} | | - |
-| multiple | 是否支持多选 | boolean | false | | - |
-| checkable | 是否支持勾选节点的复选框 | boolean | false | | - |
-| checkedKeys | (用于受控)当前勾选复选框节点 key 的数组或 `{checked: Array, indeterminate: Array}` 的对象 | \| {
checked: string[];
indeterminate: string[];
}
\| string[] | - | | - |
-| defaultCheckedKeys | (用于非受控)默认勾选复选框节点 key 的数组 | \| {
checked: string[];
indeterminate: string[];
}
\| string[] | [] | 是 | - |
-| checkStrictly | 勾选节点复选框是否完全受控(父子节点选中状态不再关联) | boolean | false | 是 | - |
-| checkedStrategy | 定义选中时回填的方式 | 'all' \| 'parent' \| 'child' | 'all' | 是 | - |
-| onCheck | 勾选或取消勾选复选框时触发的回调函数
**签名**:
**参数**:
_checkedKeys_: 勾选复选框节点key的数组
_extra_: 额外参数
**返回值**:
空 | (
checkedKeys: string[],
extra: {
checkedNodes: Array;
checkedNodesPositions: Array;
indeterminateKeys: string[];
node: NodeInstance;
checked: boolean;
key: Key;
}
) => void | () =\> \{\} | 是 | - |
-| expandedKeys | (用于受控)当前展开的节点 key 的数组 | string[] | - | | - |
-| defaultExpandedKeys | (用于非受控)默认展开的节点 key 的数组 | string[] | [] | 是 | - |
-| defaultExpandAll | 是否默认展开所有节点 | boolean | false | 是 | - |
-| autoExpandParent | 是否自动展开父节点 | boolean | true | 是 | - |
-| onExpand | 展开或收起节点时触发的回调函数
**签名**:
**参数**:
_expandedKeys_: 展开节点key的数组
_extra_: 额外参数
**返回值**:
空 | (
expandedKeys: string[],
extra: {
node: NodeInstance;
expanded: boolean;
}
) => void | () =\> \{\} | 是 | - |
-| editable | 是否支持编辑节点内容 | boolean | false | 是 | - |
-| onEditFinish | 编辑节点内容完成时触发的回调函数
**签名**:
**参数**:
_key_: 编辑节点 key
_label_: 编辑节点完成时节点的文本
_node_: 当前编辑的节点
**返回值**:
空 | (key: Key, label: string, node: NodeInstance) => void | () =\> \{\} | 是 | - |
-| draggable | 是否支持拖拽节点 | boolean | false | 是 | - |
-| onDragStart | 开始拖拽节点时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: {
event: React.MouseEvent;
node: NodeInstance;
expandedKeys: string[];
}) => void | () =\> \{\} | 是 | - |
-| onDragEnter | 拖拽节点进入目标节点时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: {
event: React.MouseEvent;
node: NodeInstance;
expandedKeys: Array;
}) => void | () =\> \{\} | 是 | - |
-| onDragOver | 拖拽节点在目标节点上移动的时候触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | 是 | - |
-| onDragLeave | 拖拽节点离开目标节点时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | 是 | - |
-| onDragEnd | 拖拽节点拖拽结束时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | 是 | - |
-| onDrop | 拖拽节点放入目标节点内或前后触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: {
event: React.MouseEvent;
node: NodeInstance;
dragNode: NodeInstance;
dragNodesKeys: Array;
dropPosition: number;
}) => void | () =\> \{\} | 是 | - |
-| canDrop | 节点是否可被作为拖拽的目标节点
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
是否可以被当作目标节点 | (info: {
event?: React.MouseEvent;
node: NodeInstance;
dragNode: NodeInstance;
dragNodesKeys: Array;
dropPosition: number;
}) => boolean | () =\> \{\} | 是 | - |
-| loadData | 异步加载数据的函数
**签名**:
**参数**:
_node_: 被点击展开的节点 | (node: NodeInstance) => Promise | - | | - |
-| filterTreeNode | 按需筛选高亮节点
**签名**:
**参数**:
_node_: 待筛选的节点
**返回值**:
是否被筛选中 | (node: NodeInstance) => boolean | - | | - |
-| onRightClick | 右键点击节点时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | 是 | - |
-| isLabelBlock | 设置节点是否占满剩余空间,一般用于统一在各节点右侧添加元素(借助 flex 实现,暂时只支持 ie10+) | boolean | false | | - |
-| isNodeBlock | 设置节点是否占满一行 | boolean \| Record | false | | - |
-| animation | 是否开启展开收起动画 | boolean | true | | - |
-| focusedKey | 当前获得焦点的子菜单或菜单项 key 值 | Key | - | | - |
-| renderChildNodes | 渲染子节点
**签名**:
**参数**:
_nodes_: 所有的子节点
**返回值**:
子节点 | (nodes: NodeElement[]) => React.ReactNode | - | | - |
-| labelRender | 渲染单个子节点
**签名**:
**参数**:
_node_: 节点数据
**返回值**:
返回节点 | (node: Record) => React.ReactNode | - | | 1.25 |
-| immutable | 是否是不可变数据 | boolean | false | | 1.23 |
-| useVirtual | 是否开启虚拟滚动 | boolean | false | | - |
+| 参数 | 说明 | 类型 | 默认值 | 是否必填 | 支持版本 |
+| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- | -------- |
+| children | 树节点 | React.ReactNode | - | | - |
+| dataSource | 数据源,该属性优先级高于 children | TreeDataType[] | - | | - |
+| showLine | 是否显示树的线 | boolean | false | | - |
+| selectable | 是否支持选中节点 | boolean | false | | - |
+| selectedKeys | (用于受控)当前选中节点 key 的数组 | string[] | - | | - |
+| defaultSelectedKeys | (用于非受控)默认选中节点 key 的数组 | string[] | [] | | - |
+| onSelect | 选中或取消选中节点时触发的回调函数
**签名**:
**参数**:
_selectedKeys_: 选中节点key的数组
_extra_: 额外参数
**返回值**:
空 | (
selectedKeys: string[],
extra: {
selectedNodes: Array\;
node: NodeInstance;
selected: boolean;
event: React.KeyboardEvent \| React.MouseEvent;
}
) => void | () =\> \{\} | | - |
+| multiple | 是否支持多选 | boolean | false | | - |
+| checkable | 是否支持勾选节点的复选框 | boolean | false | | - |
+| checkedKeys | (用于受控)当前勾选复选框节点 key 的数组或 `{checked: Array, indeterminate: Array}` 的对象 | \| {
checked: string[];
indeterminate: string[];
}
\| string[] | - | | - |
+| defaultCheckedKeys | (用于非受控)默认勾选复选框节点 key 的数组 | \| {
checked: string[];
indeterminate: string[];
}
\| string[] | [] | | - |
+| checkStrictly | 勾选节点复选框是否完全受控(父子节点选中状态不再关联) | boolean | false | | - |
+| checkedStrategy | 定义选中时回填的方式 | 'all' \| 'parent' \| 'child' | 'all' | | - |
+| onCheck | 勾选或取消勾选复选框时触发的回调函数
**签名**:
**参数**:
_checkedKeys_: 勾选复选框节点key的数组
_extra_: 额外参数
**返回值**:
空 | (
checkedKeys: string[],
extra: {
checkedNodes: Array\;
checkedNodesPositions: Array\;
indeterminateKeys: string[];
node: NodeInstance;
checked: boolean;
key: Key;
}
) => void | () =\> \{\} | | - |
+| expandedKeys | (用于受控)当前展开的节点 key 的数组 | string[] | - | | - |
+| defaultExpandedKeys | (用于非受控)默认展开的节点 key 的数组 | string[] | [] | | - |
+| defaultExpandAll | 是否默认展开所有节点 | boolean | false | | - |
+| autoExpandParent | 是否自动展开父节点 | boolean | true | | - |
+| onExpand | 展开或收起节点时触发的回调函数
**签名**:
**参数**:
_expandedKeys_: 展开节点key的数组
_extra_: 额外参数
**返回值**:
空 | (
expandedKeys: string[],
extra: {
node: NodeInstance;
expanded: boolean;
}
) => void | () =\> \{\} | | - |
+| editable | 是否支持编辑节点内容 | boolean | false | | - |
+| onEditFinish | 编辑节点内容完成时触发的回调函数
**签名**:
**参数**:
_key_: 编辑节点 key
_label_: 编辑节点完成时节点的文本
_node_: 当前编辑的节点
**返回值**:
空 | (key: Key, label: string, node: NodeInstance) => void | () =\> \{\} | | - |
+| draggable | 是否支持拖拽节点 | boolean | false | | - |
+| onDragStart | 开始拖拽节点时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: {
event: React.MouseEvent;
node: NodeInstance;
expandedKeys: string[];
}) => void | () =\> \{\} | | - |
+| onDragEnter | 拖拽节点进入目标节点时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: {
event: React.MouseEvent;
node: NodeInstance;
expandedKeys: Array\;
}) => void | () =\> \{\} | | - |
+| onDragOver | 拖拽节点在目标节点上移动的时候触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | | - |
+| onDragLeave | 拖拽节点离开目标节点时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | | - |
+| onDragEnd | 拖拽节点拖拽结束时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | | - |
+| onDrop | 拖拽节点放入目标节点内或前后触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: {
event: React.MouseEvent;
node: NodeInstance;
dragNode: NodeInstance;
dragNodesKeys: Array\;
dropPosition: number;
}) => void | () =\> \{\} | | - |
+| canDrop | 节点是否可被作为拖拽的目标节点
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
是否可以被当作目标节点 | (info: {
event?: React.MouseEvent;
node: NodeInstance;
dragNode: NodeInstance;
dragNodesKeys: Array\;
dropPosition: number;
}) => boolean | () =\> \{\} | | - |
+| loadData | 异步加载数据的函数
**签名**:
**参数**:
_node_: 被点击展开的节点 | (node: NodeInstance) => Promise\ | - | | - |
+| filterTreeNode | 按需筛选高亮节点
**签名**:
**参数**:
_node_: 待筛选的节点
**返回值**:
是否被筛选中 | (node: NodeInstance) => boolean | - | | - |
+| onRightClick | 右键点击节点时触发的回调函数
**签名**:
**参数**:
_info_: 额外参数
**返回值**:
空 | (info: { event: React.MouseEvent; node: NodeInstance }) => void | () =\> \{\} | | - |
+| isLabelBlock | 设置节点是否占满剩余空间,一般用于统一在各节点右侧添加元素(借助 flex 实现,暂时只支持 ie10+) | boolean | false | | - |
+| isNodeBlock | 设置节点是否占满一行 | boolean \| Record\ | false | | - |
+| animation | 是否开启展开收起动画 | boolean | true | | - |
+| focusedKey | 当前获得焦点的子菜单或菜单项 key 值 | Key | - | | - |
+| renderChildNodes | 渲染子节点
**签名**:
**参数**:
_nodes_: 所有的子节点
**返回值**:
子节点 | (nodes: NodeElement[]) => React.ReactNode | - | | - |
+| labelRender | 渲染单个子节点
**签名**:
**参数**:
_node_: 节点数据
**返回值**:
返回节点 | (node: Record\) => React.ReactNode | - | | 1.25 |
+| immutable | 是否是不可变数据 | boolean | false | | 1.23 |
+| useVirtual | 是否开启虚拟滚动 | boolean | false | | - |
### Tree.Node
diff --git a/components/tree/types.ts b/components/tree/types.ts
index f0820354b7..b6b55e7b4f 100644
--- a/components/tree/types.ts
+++ b/components/tree/types.ts
@@ -3,6 +3,7 @@ import type { CommonProps } from '../util';
import type { VirtualListProps } from '../virtual-list';
import type { Tree } from './view/tree';
import type { TreeNode } from './view/tree-node';
+import type { DataSourceItem } from '../select';
export type Key = string;
export type KeyEntities = Record;
@@ -252,6 +253,13 @@ export interface NodeProps extends CommonProps {
* @skip
*/
style?: React.CSSProperties;
+
+ /**
+ * value
+ * @en value
+ * @skip
+ */
+ value?: DataSourceItem;
}
export type NodeElement = React.ReactElement;
diff --git a/components/tree/view/util.ts b/components/tree/view/util.ts
index b8e9caf3bf..ed83aef501 100644
--- a/components/tree/view/util.ts
+++ b/components/tree/view/util.ts
@@ -1,4 +1,5 @@
import type { DataNode, FieldDataNode, Key, KeyEntities, NodeElement } from '../types';
+import type { KeyEntities as TreeSelectKeyEntities } from '../../tree-select/types';
import TreeNode from './tree-node';
// export function normalizeToArray(keys: undefined | null): [];