From e5450c69a47771bd4bac33a79f71df9b5d30452c Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 14 Aug 2019 16:37:09 +0800
Subject: [PATCH 001/397] chore: use map instead of object mapping

---
 src/util.tsx | 43 +++++++++++++++++++++++--------------------
 1 file changed, 23 insertions(+), 20 deletions(-)

diff --git a/src/util.tsx b/src/util.tsx
index a3140917..ab871fde 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -178,20 +178,21 @@ export function conductCheck(
 ) {
   const warningMissKeys: Key[] = [];
 
-  const checkedKeys = {};
-  const halfCheckedKeys = {}; // Record the key has some child checked (include child half checked)
+  const checkedKeyMap: Map<Key, boolean> = new Map();
+  // Record the key has some child checked (include child half checked)
+  const halfCheckedKeyMap: Map<Key, boolean> = new Map();
 
   (checkStatus.checkedKeys || []).forEach(key => {
-    checkedKeys[key] = true;
+    checkedKeyMap.set(key, true);
   });
 
   (checkStatus.halfCheckedKeys || []).forEach(key => {
-    halfCheckedKeys[key] = true;
+    halfCheckedKeyMap.set(key, true);
   });
 
   // Conduct up
   function conductUp(key: Key) {
-    if (checkedKeys[key] === isCheck) return;
+    if (checkedKeyMap.get(key) === isCheck) return;
 
     const entity = keyEntities[key];
     if (!entity) return;
@@ -204,23 +205,25 @@ export function conductCheck(
     let everyChildChecked = true;
     let someChildChecked = false; // Child checked or half checked
 
-    (children || [])
-      .filter(child => !isCheckDisabled(child.node))
-      .forEach(({ key: childKey }) => {
-        const childChecked = checkedKeys[childKey];
-        const childHalfChecked = halfCheckedKeys[childKey];
+    if (children && children.length) {
+      children.forEach(({ key: childKey, node: childNode }) => {
+        if (isCheckDisabled(childNode)) return;
+
+        const childChecked = checkedKeyMap.get(childKey);
+        const childHalfChecked = halfCheckedKeyMap.get(childKey);
 
         if (childChecked || childHalfChecked) someChildChecked = true;
         if (!childChecked) everyChildChecked = false;
       });
+    }
 
     // Update checked status
     if (isCheck) {
-      checkedKeys[key] = everyChildChecked;
+      checkedKeyMap.set(key, everyChildChecked);
     } else {
-      checkedKeys[key] = false;
+      checkedKeyMap.set(key, false);
     }
-    halfCheckedKeys[key] = someChildChecked;
+    halfCheckedKeyMap.set(key, someChildChecked);
 
     if (parent) {
       conductUp(parent.key);
@@ -229,7 +232,7 @@ export function conductCheck(
 
   // Conduct down
   function conductDown(key: Key) {
-    if (checkedKeys[key] === isCheck) return;
+    if (checkedKeyMap.get(key) === isCheck) return;
 
     const entity = keyEntities[key];
     if (!entity) return;
@@ -238,7 +241,7 @@ export function conductCheck(
 
     if (isCheckDisabled(node)) return;
 
-    checkedKeys[key] = isCheck;
+    checkedKeyMap.set(key, isCheck);
 
     (children || []).forEach(child => {
       conductDown(child.key);
@@ -254,7 +257,7 @@ export function conductCheck(
     }
 
     const { children, parent, node } = entity;
-    checkedKeys[key] = isCheck;
+    checkedKeyMap.set(key, isCheck);
 
     if (isCheckDisabled(node)) return;
 
@@ -279,15 +282,15 @@ export function conductCheck(
   const halfCheckedKeyList = [];
 
   // Fill checked list
-  Object.keys(checkedKeys).forEach(key => {
-    if (checkedKeys[key]) {
+  checkedKeyMap.forEach((checked, key) => {
+    if (checked) {
       checkedKeyList.push(key);
     }
   });
 
   // Fill half checked list
-  Object.keys(halfCheckedKeys).forEach(key => {
-    if (!checkedKeys[key] && halfCheckedKeys[key]) {
+  halfCheckedKeyMap.forEach((halfChecked, key) => {
+    if (!checkedKeyMap.get(key) && halfChecked) {
       halfCheckedKeyList.push(key);
     }
   });

From ad5f15f5ed0433d12518d0f38f1742fdad712e38 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 14 Aug 2019 16:37:54 +0800
Subject: [PATCH 002/397] v3.0.0-alpha.15

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4114f9e9..b9ffbe04 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.14",
+  "version": "3.0.0-alpha.15",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From f0165bb47318250b8b61acda3e8820b46fa1732a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E5=B8=85?= <qixian.cs@outlook.com>
Date: Thu, 29 Aug 2019 13:50:03 +0800
Subject: [PATCH 003/397] treeNode should disabled when tree disabled

---
 src/TreeNode.tsx                           | 5 -----
 tests/TreeProps.spec.js                    | 2 +-
 tests/__snapshots__/TreeProps.spec.js.snap | 4 ++--
 3 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index d94628ea..be8aad2a 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -297,11 +297,6 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       context: { disabled: treeDisabled },
     } = this.props;
 
-    // Follow the logic of Selectable
-    if (disabled === false) {
-      return false;
-    }
-
     return !!(treeDisabled || disabled);
   };
 
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index 68e1f4ec..a691ac93 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -784,7 +784,7 @@ describe('Tree Props', () => {
       expect(wrapper).toMatchSnapshot();
     });
 
-    it('treeNode not disabled', () => {
+    it('treeNode should disabled when tree disabled', () => {
       const wrapper = render(
         <Tree defaultExpandAll disabled>
           <TreeNode key="0-0" disabled={false} />
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index 917ae6ce..e6f9141e 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -657,7 +657,7 @@ exports[`Tree Props disabled basic 1`] = `
 </div>
 `;
 
-exports[`Tree Props disabled treeNode not disabled 1`] = `
+exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
 <div
   class="rc-tree-list rc-tree"
   role="tree"
@@ -668,7 +668,7 @@ exports[`Tree Props disabled treeNode not disabled 1`] = `
       style="display:flex;flex-direction:column"
     >
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
+        class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
         role="treeitem"
         tabindex="0"
       >

From aa6f0c2d186a1249643984eccd55bdb35b0d9e3e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E5=B8=85?= <qixian.cs@outlook.com>
Date: Thu, 29 Aug 2019 13:52:14 +0800
Subject: [PATCH 004/397] v3.0.0-alpha.16

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b9ffbe04..b1ec25c3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.15",
+  "version": "3.0.0-alpha.16",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 363d84b83cc9f0dd28aee43604f153a002069ba9 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sun, 15 Sep 2019 13:38:31 +0800
Subject: [PATCH 005/397] fix: Tree crash if tree node remove without node
 entity fix ant-design/ant-design#18805

---
 src/utils/treeUtil.ts |  4 +++-
 tests/util.spec.js    | 36 +++++++++++++++++++++++++++++++++---
 2 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index d4ad6be7..20075e01 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -253,6 +253,8 @@ export function getTreeNodeProps(
     keyEntities,
   }: TreeNodeRequiredProps,
 ) {
+  const entity = keyEntities[key];
+
   const treeNodeProps = {
     eventKey: key,
     expanded: expandedKeys.indexOf(key) !== -1,
@@ -261,7 +263,7 @@ export function getTreeNodeProps(
     loading: loadingKeys.indexOf(key) !== -1,
     checked: checkedKeys.indexOf(key) !== -1,
     halfChecked: halfCheckedKeys.indexOf(key) !== -1,
-    pos: String(keyEntities[key].pos),
+    pos: String(entity ? entity.pos : ''),
 
     // [Legacy] Drag props
     dragOver: dragOverNodeKey === key && dropPosition === 0,
diff --git a/tests/util.spec.js b/tests/util.spec.js
index 4c5d83dc..e765057c 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -1,9 +1,7 @@
 /* eslint-disable no-undef, react/no-multi-comp,
 react/no-unused-state, react/prop-types, no-return-assign */
 import React from 'react';
-import { mount } from 'enzyme';
 import Tree, { TreeNode } from '../src';
-import { InternalTreeNode } from '../src/TreeNode';
 import {
   convertDataToTree,
   conductCheck,
@@ -12,7 +10,12 @@ import {
   getDataAndAria,
   parseCheckedKeys,
 } from '../src/util';
-import { flattenTreeData, convertTreeToData, convertDataToEntities } from '../src/utils/treeUtil';
+import {
+  flattenTreeData,
+  convertTreeToData,
+  convertDataToEntities,
+  getTreeNodeProps,
+} from '../src/utils/treeUtil';
 import { spyConsole, spyError } from './util';
 
 describe('Util', () => {
@@ -340,4 +343,31 @@ describe('Util', () => {
       '1',
     ]);
   });
+
+  it('not crash if node not exist with getTreeNodeProps', () => {
+    const nodeProps = getTreeNodeProps('not-exist', {
+      expandedKeys: [],
+      selectedKeys: [],
+      loadedKeys: [],
+      loadingKeys: [],
+      checkedKeys: [],
+      halfCheckedKeys: [],
+      dragOverNodeKey: null,
+      dropPosition: null,
+      keyEntities: {},
+    });
+
+    expect(nodeProps).toEqual(
+      expect.objectContaining({
+        checked: false,
+        eventKey: 'not-exist',
+        expanded: false,
+        halfChecked: false,
+        loaded: false,
+        loading: false,
+        pos: '',
+        selected: false,
+      }),
+    );
+  });
 });

From 0180317ea82f0bebad42bb291c90d8d510f8ab6c Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sun, 15 Sep 2019 13:40:08 +0800
Subject: [PATCH 006/397] v3.0.0-alpha.17

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b1ec25c3..f6c7dafb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.16",
+  "version": "3.0.0-alpha.17",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 80ad1323a11483781a26415863016292600e99e1 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 16 Sep 2019 12:10:42 +0800
Subject: [PATCH 007/397] fix: number key should work fix
 ant-design/ant-design#18812

---
 src/NodeList.tsx      |  2 +-
 src/Tree.tsx          |  2 +-
 src/util.tsx          | 12 ------------
 src/utils/treeUtil.ts |  2 +-
 tests/Tree.spec.js    | 37 +++++++++++++++++++++++++++++++++++++
 5 files changed, 40 insertions(+), 15 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index eadf0678..af531a45 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -77,7 +77,7 @@ function itemKey(item: FlattenNode) {
     data: { key },
     pos,
   } = item;
-  return getKey(key, pos);
+  return String(getKey(key, pos));
 }
 
 const NodeList: React.FC<NodeListProps> = ({
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 3c43b7ab..48214845 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -287,7 +287,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     } else if (!prevProps && props.defaultExpandAll) {
       const cloneKeyEntities = { ...keyEntities };
       delete cloneKeyEntities[MOTION_KEY];
-      newState.expandedKeys = Object.keys(cloneKeyEntities);
+      newState.expandedKeys = Object.keys(cloneKeyEntities).map(key => cloneKeyEntities[key].key);
     } else if (!prevProps && props.defaultExpandedKeys) {
       newState.expandedKeys =
         props.autoExpandParent || props.defaultExpandParent
diff --git a/src/util.tsx b/src/util.tsx
index ab871fde..7be4c954 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -97,15 +97,6 @@ export function calcSelectedKeys(selectedKeys: Key[], props: TreeProps) {
   return selectedKeys;
 }
 
-/**
- * Since React internal will convert key to string,
- * we need do this to avoid `checkStrictly` use number match
- */
-function keyListToString(keyList: Key[]) {
-  if (!keyList) return keyList;
-  return keyList.map(key => String(key));
-}
-
 const internalProcessProps = (props: DataNode): Partial<TreeNodeProps> => props;
 export function convertDataToTree(
   treeData: DataNode[],
@@ -156,9 +147,6 @@ export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Ke
     return null;
   }
 
-  keyProps.checkedKeys = keyListToString(keyProps.checkedKeys);
-  keyProps.halfCheckedKeys = keyListToString(keyProps.halfCheckedKeys);
-
   return keyProps;
 }
 
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 20075e01..36bc7ecf 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -6,7 +6,7 @@ import { getPosition, isTreeNode } from '../util';
 
 export function getKey(key: Key, pos: string) {
   if (key !== null && key !== undefined) {
-    return String(key);
+    return key;
   }
   return pos;
 }
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index d19a337c..022d0f02 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1224,4 +1224,41 @@ describe('Tree Basic', () => {
       );
     });
   });
+
+  it('Number key', () => {
+    const onCheck = jest.fn();
+
+    const Demo = () => {
+      const [checkedKeys, setCheckedKeys] = React.useState([]);
+
+      return (
+        <Tree
+          checkable
+          defaultExpandAll
+          checkedKeys={checkedKeys}
+          onCheck={keys => {
+            setCheckedKeys(keys);
+            onCheck(keys);
+          }}
+          treeData={[{ key: 11, title: 11, children: [{ key: 22, title: 22 }] }]}
+        />
+      );
+    };
+
+    const wrapper = mount(<Demo />);
+    wrapper.update();
+
+    wrapper
+      .find('.rc-tree-checkbox')
+      .first()
+      .simulate('click');
+    expect(onCheck).toHaveBeenCalledWith([11, 22]);
+
+    onCheck.mockReset();
+    wrapper
+      .find('.rc-tree-checkbox')
+      .last()
+      .simulate('click');
+    expect(onCheck).toHaveBeenCalledWith([]);
+  });
 });

From ac18d4e200b9ddc39f08941f4c7b5b979427ab1a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 16 Sep 2019 12:11:47 +0800
Subject: [PATCH 008/397] v3.0.0-alpha.18

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f6c7dafb..932978f4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.17",
+  "version": "3.0.0-alpha.18",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 38336bd999e9ca5a01ab478492a53e6f237e3f4c Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 16 Sep 2019 14:55:35 +0800
Subject: [PATCH 009/397] feat: Flatten support `true` for all expanded

---
 src/utils/treeUtil.ts |  7 +++--
 tests/util.spec.js    | 68 ++++++++++++++++++++++++++++++-------------
 2 files changed, 52 insertions(+), 23 deletions(-)

diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 36bc7ecf..3299d88d 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -76,10 +76,13 @@ export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
 
 /**
  * Flat nest tree data into flatten list. This is used for virtual list render.
+ * @param treeNodeList Origin data node list
+ * @param expandedKeys
+ * need expanded keys, provides `true` means all expanded (used in `rc-tree-select`).
  */
 export function flattenTreeData(
   treeNodeList: DataNode[] = [],
-  expandedKeys: Key[] = [],
+  expandedKeys: Key[] | true = [],
 ): FlattenNode[] {
   const flattenList: FlattenNode[] = [];
 
@@ -102,7 +105,7 @@ export function flattenTreeData(
       flattenList.push(flattenNode);
 
       // Loop treeNode children
-      if (expandedKeys.includes(mergedKey)) {
+      if (expandedKeys === true || expandedKeys.includes(mergedKey)) {
         flattenNode.children = dig(treeNode.children || [], flattenNode);
       } else {
         flattenNode.children = [];
diff --git a/tests/util.spec.js b/tests/util.spec.js
index e765057c..38fad516 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -312,7 +312,7 @@ describe('Util', () => {
     errorSpy.mockRestore();
   });
 
-  it('flatten treeNode', () => {
+  describe('flatten treeNode', () => {
     function getNode(key, children) {
       return {
         key,
@@ -321,27 +321,53 @@ describe('Util', () => {
       };
     }
 
-    const flattenList = flattenTreeData(
-      [
-        getNode('0', [
-          getNode('0-0'),
-          getNode('0-1'),
-          getNode('0-2', [getNode('0-2-0'), getNode('0-2-1')]),
-        ]),
-        getNode('1'),
-      ],
-      ['0-2', '0'],
-    );
+    it('with expanded keys', () => {
+      const flattenList = flattenTreeData(
+        [
+          getNode('0', [
+            getNode('0-0'),
+            getNode('0-1'),
+            getNode('0-2', [getNode('0-2-0'), getNode('0-2-1')]),
+          ]),
+          getNode('1'),
+        ],
+        ['0-2', '0'],
+      );
 
-    expect(flattenList.map(({ key }) => key)).toEqual([
-      '0',
-      '0-0',
-      '0-1',
-      '0-2',
-      '0-2-0',
-      '0-2-1',
-      '1',
-    ]);
+      expect(flattenList.map(({ key }) => key)).toEqual([
+        '0',
+        '0-0',
+        '0-1',
+        '0-2',
+        '0-2-0',
+        '0-2-1',
+        '1',
+      ]);
+    });
+
+    it('with all', () => {
+      const flattenList = flattenTreeData(
+        [
+          getNode('0', [
+            getNode('0-0'),
+            getNode('0-1'),
+            getNode('0-2', [getNode('0-2-0'), getNode('0-2-1')]),
+          ]),
+          getNode('1'),
+        ],
+        true,
+      );
+
+      expect(flattenList.map(({ key }) => key)).toEqual([
+        '0',
+        '0-0',
+        '0-1',
+        '0-2',
+        '0-2-0',
+        '0-2-1',
+        '1',
+      ]);
+    });
   });
 
   it('not crash if node not exist with getTreeNodeProps', () => {

From d5bdfdc31ad8f84bb934cbece502c636988e33f9 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 16 Sep 2019 14:56:46 +0800
Subject: [PATCH 010/397] v3.0.0-alpha.19

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 932978f4..709858a8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.18",
+  "version": "3.0.0-alpha.19",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 405999d8d51ea7b370c39a233a56f351f3b7eb9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 17 Sep 2019 17:29:18 +0800
Subject: [PATCH 011/397] feat: Accessibility & keyboard support (#275)

* init

* up & down support

* adjust node define

* basic test

* refactor with props warning

* support keyboard

* accessiblity start

* add input for accessibility

* add test case

* fix lint
---
 .eslintrc.js                                  |    1 +
 assets/index.less                             |   25 +-
 examples/animation.jsx                        |    2 +-
 examples/animation.less                       |   52 +-
 examples/basic.jsx                            |   13 +-
 examples/funtionTitle.jsx                     |    2 +-
 package.json                                  |    5 +-
 src/MotionTreeNode.tsx                        |    5 +-
 src/NodeList.tsx                              |  253 +-
 src/Tree.tsx                                  |  198 +-
 src/TreeNode.tsx                              |   31 +-
 src/contextTypes.ts                           |   30 +-
 src/interface.tsx                             |   18 +-
 src/util.tsx                                  |    6 +-
 src/utils/treeUtil.ts                         |   51 +-
 tests/Accessibility.spec.js                   |  187 ++
 tests/Tree.spec.js                            |  164 +-
 tests/TreeProps.spec.js                       |  112 +-
 tests/__snapshots__/Tree.spec.js.snap         |  901 +++---
 .../__snapshots__/TreeNodeProps.spec.js.snap  | 1163 ++++----
 tests/__snapshots__/TreeProps.spec.js.snap    | 2641 +++++++++--------
 21 files changed, 3313 insertions(+), 2547 deletions(-)
 create mode 100644 tests/Accessibility.spec.js

diff --git a/.eslintrc.js b/.eslintrc.js
index 9bb4e75c..5fd84791 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -4,6 +4,7 @@ module.exports = {
   ...base,
   rules: {
     ...base.rules,
+    'default-case': 0,
     'eslint-comments/disable-enable-pair': 0,
     'react/require-default-props': 0,
     'react/no-unused-prop-types': 1,
diff --git a/assets/index.less b/assets/index.less
index 9b99ae2d..6af0112e 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -1,8 +1,16 @@
-@treePrefixCls: rc-tree;
+@treePrefixCls: ~'rc-tree';
+@treeNodePrefixCls: ~'@{treePrefixCls}-treenode';
+
 .@{treePrefixCls} {
   margin: 0;
+  border: 1px solid transparent;
+  
+  &-focused {
+    border-color: cyan;
+  }
+
   // padding: 5px;
-  [role='treeitem'] {
+  .@{treeNodePrefixCls} {
     margin: 0;
     padding: 0;
     line-height: 20px;
@@ -134,14 +142,14 @@
     }
   }
   &:not(.@{treePrefixCls}-show-line) {
-    [role='treeitem'] {
+    .@{treeNodePrefixCls} {
       .@{treePrefixCls}-switcher-noop {
         background: none;
       }
     }
   }
   &.@{treePrefixCls}-show-line {
-    [role='treeitem']:not(:last-child) {
+    .@{treeNodePrefixCls}:not(:last-child) {
       > ul {
         background: url('data:image/gif;base64,R0lGODlhCQACAIAAAMzMzP///yH5BAEAAAEALAAAAAAJAAIAAAIEjI9pUAA7')
           0 0 repeat-y;
@@ -150,7 +158,7 @@
         background-position: -56px -18px;
       }
     }
-    [role='treeitem']:last-child {
+    .@{treeNodePrefixCls}:last-child {
       > .@{treePrefixCls}-switcher-noop {
         background-position: -56px -36px;
       }
@@ -170,6 +178,13 @@
       cursor: not-allowed;
     }
   }
+  &-treenode-active {
+    background: rgba(0, 0, 0, 0.1);
+
+    // .@{treePrefixCls}-node-content-wrapper {
+    //   background: rgba(0, 0, 0, 0.1);
+    // }
+  }
   &-node-selected {
     background-color: #ffe6b0;
     border: 1px #ffb951 solid;
diff --git a/examples/animation.jsx b/examples/animation.jsx
index 15d82540..3b406208 100644
--- a/examples/animation.jsx
+++ b/examples/animation.jsx
@@ -88,7 +88,7 @@ function getTreeData() {
 }
 
 const Demo = () => (
-  <div>
+  <div className="animation">
     <h2>expanded</h2>
     <style dangerouslySetInnerHTML={{ __html: STYLE }} />
 
diff --git a/examples/animation.less b/examples/animation.less
index fefd1c5e..6cc0a798 100644
--- a/examples/animation.less
+++ b/examples/animation.less
@@ -1,37 +1,39 @@
-.rc-tree-treenode {
-  display: flex;
+.animation {
+  .rc-tree-treenode {
+    display: flex;
 
-  .rc-tree-indent {
-    position: relative;
-    align-self: stretch;
-
-    .rc-tree-indent-unit {
+    .rc-tree-indent {
       position: relative;
-      height: 100%;
+      align-self: stretch;
 
-      &::before {
-        position: absolute;
-        top: 100%;
-        right: -26px;
-        bottom: -100%;
-        border-right: 1px solid blue;
-        content: '';
-      }
+      .rc-tree-indent-unit {
+        position: relative;
+        height: 100%;
 
-      // End should ignore
-      &-end {
         &::before {
-          display: none;
+          position: absolute;
+          top: 100%;
+          right: -26px;
+          bottom: -100%;
+          border-right: 1px solid blue;
+          content: '';
+        }
+
+        // End should ignore
+        &-end {
+          &::before {
+            display: none;
+          }
         }
       }
     }
-  }
 
-  // Motion
-  &-motion:not(.node-motion-appear-active) {
-    .rc-tree-indent-unit {
-      &::before {
-        display: none;
+    // Motion
+    &-motion:not(.node-motion-appear-active) {
+      .rc-tree-indent-unit {
+        &::before {
+          display: none;
+        }
       }
     }
   }
diff --git a/examples/basic.jsx b/examples/basic.jsx
index 2909de7a..2d1d49de 100644
--- a/examples/basic.jsx
+++ b/examples/basic.jsx
@@ -51,13 +51,6 @@ class Demo extends React.Component {
   onSelect = (selectedKeys, info) => {
     console.log('selected', selectedKeys, info);
     this.selKey = info.node.props.eventKey;
-
-    if (this.tree) {
-      console.log(
-        'Selected DOM node:',
-        selectedKeys.map(key => ReactDOM.findDOMNode(this.tree.domTreeNodes[key])),
-      );
-    }
   };
 
   onCheck = (checkedKeys, info) => {
@@ -102,6 +95,7 @@ class Demo extends React.Component {
     return (
       <div style={{ margin: '0 20px' }}>
         <h2>simple</h2>
+        <input aria-label="good" />
         <Tree
           ref={this.setTreeRef}
           className="myCls"
@@ -145,6 +139,11 @@ class Demo extends React.Component {
           onCheck={this.onCheck}
           treeData={treeData}
         />
+
+        <h2>Select</h2>
+        <Tree className="myCls" defaultExpandAll treeData={treeData} onSelect={this.onSelect} />
+
+        <input aria-label="bad" />
       </div>
     );
   }
diff --git a/examples/funtionTitle.jsx b/examples/funtionTitle.jsx
index 6fec7eca..0f8283cf 100644
--- a/examples/funtionTitle.jsx
+++ b/examples/funtionTitle.jsx
@@ -71,7 +71,7 @@ function getTreeData() {
 const Demo = () => {
   const [keys, setKeys] = useState(data.map(item => item.fieldName));
   return (
-    <div>
+    <div className="animation">
       <h2>expanded</h2>
       <style dangerouslySetInnerHTML={{ __html: STYLE }} />
 
diff --git a/package.json b/package.json
index 709858a8..83a23c36 100644
--- a/package.json
+++ b/package.json
@@ -65,9 +65,8 @@
     "classnames": "2.x",
     "prop-types": "^15.5.8",
     "rc-animate": "^2.9.2",
-    "rc-util": "^4.9.0",
+    "rc-util": "^4.11.0",
     "rc-virtual-list": "^0.0.0-alpha.18",
-    "react-lifecycles-compat": "^3.0.4",
-    "warning": "^4.0.3"
+    "react-lifecycles-compat": "^3.0.4"
   }
 }
diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index ee306dca..484eb037 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -8,6 +8,7 @@ import { getTreeNodeProps, TreeNodeRequiredProps } from './utils/treeUtil';
 import { TreeContext } from './contextTypes';
 
 interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
+  active: boolean;
   motion?: any;
   motionNodes?: FlattenNode[];
   onMotionEnd: () => void;
@@ -24,6 +25,7 @@ const MotionTreeNode: React.FC<MotionTreeNodeProps> = (
     motionNodes,
     motionType,
     onMotionEnd,
+    active,
     treeNodeRequiredProps,
     ...props
   },
@@ -68,6 +70,7 @@ const MotionTreeNode: React.FC<MotionTreeNodeProps> = (
                 <TreeNode
                   {...restProps}
                   {...treeNodeProps}
+                  active={active}
                   data={treeNode.data}
                   key={key}
                   isStart={isStart}
@@ -80,7 +83,7 @@ const MotionTreeNode: React.FC<MotionTreeNodeProps> = (
       </CSSMotion>
     );
   }
-  return <TreeNode domRef={ref} className={className} style={style} {...props} />;
+  return <TreeNode domRef={ref} className={className} style={style} {...props} active={active} />;
 };
 
 MotionTreeNode.displayName = 'MotionTreeNode';
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index af531a45..4814a258 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -4,10 +4,14 @@
 
 import * as React from 'react';
 import VirtualList from 'rc-virtual-list';
+import KeyCode from 'rc-util/lib/KeyCode';
 import { FlattenNode, Key, DataEntity, DataNode } from './interface';
 import MotionTreeNode from './MotionTreeNode';
+import { TreeContext } from './contextTypes';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
-import { getTreeNodeProps, getKey } from './utils/treeUtil';
+import { getTreeNodeProps, getKey, convertNodePropsToEventData } from './utils/treeUtil';
+
+const HIDDEN_STYLE = { width: 0, height: 0, display: 'flex', overflow: 'hidden', opacity: 0 };
 
 export const MOTION_KEY = `RC_TREE_MOTION_${Math.random()}`;
 
@@ -39,6 +43,11 @@ interface NodeListProps {
   style: React.CSSProperties;
   data: FlattenNode[];
   motion: any;
+  focusable?: boolean;
+  focused?: boolean;
+  tabIndex: number;
+  checkable?: boolean;
+  selectable?: boolean;
 
   expandedKeys: Key[];
   selectedKeys: Key[];
@@ -55,6 +64,10 @@ interface NodeListProps {
   // Virtual list
   height: number;
   itemHeight: number;
+
+  onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
+  onFocus?: React.FocusEventHandler<HTMLDivElement>;
+  onBlur?: React.FocusEventHandler<HTMLDivElement>;
 }
 
 /**
@@ -80,9 +93,23 @@ function itemKey(item: FlattenNode) {
   return String(getKey(key, pos));
 }
 
+function getAccessibilityPath(item: FlattenNode): string {
+  let path = String(item.data.key);
+  let current = item;
+
+  while (current.parent) {
+    current = current.parent;
+    path = `${current.data.key} > ${path}`;
+  }
+
+  return path;
+}
+
 const NodeList: React.FC<NodeListProps> = ({
   prefixCls,
   data,
+  selectable,
+  checkable,
   expandedKeys,
   selectedKeys,
   checkedKeys,
@@ -99,8 +126,17 @@ const NodeList: React.FC<NodeListProps> = ({
   height,
   itemHeight,
 
+  focusable,
+  focused,
+  tabIndex,
+
+  onKeyDown,
+  onFocus,
+  onBlur,
+
   ...domProps
 }) => {
+  // ============================== Motion ==============================
   const [disableVirtual, setDisableVirtual] = React.useState(false);
   const [prevExpandedKeys, setPrevExpandedKeys] = React.useState(expandedKeys);
   const [prevData, setPrevData] = React.useState(data);
@@ -184,47 +220,182 @@ const NodeList: React.FC<NodeListProps> = ({
     keyEntities,
   };
 
+  // =========================== Accessibility ==========================
+  const [activeKey, setActiveKey] = React.useState<Key>(null);
+
+  const activeItem = data.find(({ data: { key } }) => key === activeKey);
+
+  const changeActive = (offset: number) => {
+    let index = data.findIndex(({ data: { key } }) => key === activeKey);
+
+    // Align with index
+    if (index === -1 && offset < 0) {
+      index = data.length;
+    }
+
+    index = (index + offset + data.length) % data.length;
+
+    const item = data[index];
+    if (item) {
+      setActiveKey(item.data.key);
+    } else {
+      setActiveKey(null);
+    }
+  };
+
+  // Clean up `activeKey` if blur
+  React.useEffect(() => {
+    if (!focused) {
+      setActiveKey(null);
+    }
+  }, [focused]);
+
+  // ============================= Keyboard =============================
+  const { onNodeExpand, onNodeCheck, onNodeSelect } = React.useContext(TreeContext);
+
+  const onInternalKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
+    // >>>>>>>>>> Direction
+    switch (event.which) {
+      case KeyCode.UP: {
+        changeActive(-1);
+        event.preventDefault();
+        break;
+      }
+      case KeyCode.DOWN: {
+        changeActive(1);
+        event.preventDefault();
+        break;
+      }
+    }
+
+    if (activeItem && activeItem.data) {
+      // >>>>>>>>>> Expand & Selection
+      const expandable =
+        activeItem.data.isLeaf === false || !!(activeItem.data.children || []).length;
+      const eventNode = convertNodePropsToEventData({
+        ...getTreeNodeProps(activeKey, treeNodeRequiredProps),
+        data: activeItem.data,
+        active: true,
+      });
+
+      switch (event.which) {
+        // >>> Expand
+        case KeyCode.LEFT: {
+          // Collapse if possible
+          if (expandable && expandedKeys.includes(activeKey)) {
+            onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
+          } else if (activeItem.parent) {
+            setActiveKey(activeItem.parent.data.key);
+          }
+          event.preventDefault();
+          break;
+        }
+        case KeyCode.RIGHT: {
+          // Expand if possible
+          if (expandable && !expandedKeys.includes(activeKey)) {
+            onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
+          } else if (activeItem.children && activeItem.children.length) {
+            setActiveKey(activeItem.children[0].data.key);
+          }
+          event.preventDefault();
+          break;
+        }
+
+        // Selection
+        case KeyCode.ENTER:
+        case KeyCode.SPACE: {
+          if (
+            checkable &&
+            !eventNode.disabled &&
+            eventNode.checkable !== false &&
+            !eventNode.disableCheckbox
+          ) {
+            onNodeCheck(
+              {} as React.MouseEvent<HTMLDivElement>,
+              eventNode,
+              !checkedKeys.includes(activeKey),
+            );
+          } else if (
+            !checkable &&
+            selectable &&
+            !eventNode.disabled &&
+            eventNode.selectable !== false
+          ) {
+            onNodeSelect({} as React.MouseEvent<HTMLDivElement>, eventNode);
+          }
+          break;
+        }
+      }
+    }
+
+    if (onKeyDown) {
+      onKeyDown(event);
+    }
+  };
+
   return (
-    <VirtualList
-      {...domProps}
-      disabled={disableVirtual}
-      role="tree"
-      data={mergedData}
-      itemKey={itemKey}
-      height={height}
-      itemHeight={itemHeight}
-      onSkipRender={onMotionEnd}
-      prefixCls={`${prefixCls}-list`}
-    >
-      {(treeNode: FlattenNode) => {
-        const {
-          pos,
-          data: { key, ...restProps },
-          isStart,
-          isEnd,
-        } = treeNode;
-        const mergedKey = getKey(key, pos);
-        delete restProps.children;
-
-        const treeNodeProps = getTreeNodeProps(mergedKey, treeNodeRequiredProps);
-
-        return (
-          <MotionTreeNode
-            {...restProps}
-            {...treeNodeProps}
-            pos={pos}
-            data={treeNode.data}
-            isStart={isStart}
-            isEnd={isEnd}
-            motion={motion}
-            motionNodes={key === MOTION_KEY ? transitionRange : null}
-            motionType={motionType}
-            onMotionEnd={onMotionEnd}
-            treeNodeRequiredProps={treeNodeRequiredProps}
-          />
-        );
-      }}
-    </VirtualList>
+    <>
+      {focused && activeItem && (
+        <span style={HIDDEN_STYLE} aria-live="assertive">
+          {getAccessibilityPath(activeItem)}
+        </span>
+      )}
+
+      <div role="tree">
+        <input
+          style={HIDDEN_STYLE}
+          disabled={focusable === false}
+          tabIndex={focusable !== false ? tabIndex : null}
+          onKeyDown={onInternalKeyDown}
+          onFocus={onFocus}
+          onBlur={onBlur}
+        />
+      </div>
+
+      <VirtualList
+        {...domProps}
+        disabled={disableVirtual}
+        data={mergedData}
+        itemKey={itemKey}
+        height={height}
+        itemHeight={itemHeight}
+        onSkipRender={onMotionEnd}
+        prefixCls={`${prefixCls}-list`}
+      >
+        {(treeNode: FlattenNode) => {
+          const {
+            pos,
+            data: { key, ...restProps },
+            isStart,
+            isEnd,
+          } = treeNode;
+          const mergedKey = getKey(key, pos);
+          delete restProps.children;
+
+          const treeNodeProps = getTreeNodeProps(mergedKey, treeNodeRequiredProps);
+
+          return (
+            <MotionTreeNode
+              {...restProps}
+              {...treeNodeProps}
+              active={focused && key === activeKey}
+              pos={pos}
+              data={treeNode.data}
+              isStart={isStart}
+              isEnd={isEnd}
+              motion={motion}
+              motionNodes={key === MOTION_KEY ? transitionRange : null}
+              motionType={motionType}
+              onMotionEnd={onMotionEnd}
+              treeNodeRequiredProps={treeNodeRequiredProps}
+              onMouseMove={() => {
+                setActiveKey(null);
+              }}
+            />
+          );
+        }}
+      </VirtualList>
+    </>
   );
 };
 
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 48214845..a221edb1 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -3,7 +3,7 @@
 
 import * as React from 'react';
 import PropTypes from 'prop-types';
-import warning from 'warning';
+import warning from 'rc-util/lib/warning';
 import classNames from 'classnames';
 import { polyfill } from 'react-lifecycles-compat';
 
@@ -20,18 +20,27 @@ import {
   posToArr,
   conductCheck,
 } from './util';
-import { DataNode, IconType, Key, FlattenNode, DataEntity, NodeInstance } from './interface';
+import {
+  DataNode,
+  IconType,
+  Key,
+  FlattenNode,
+  DataEntity,
+  EventDataNode,
+  NodeInstance,
+} from './interface';
 import {
   flattenTreeData,
   convertTreeToData,
   convertDataToEntities,
   warningWithoutKey,
+  convertNodePropsToEventData,
 } from './utils/treeUtil';
 import NodeList, { MOTION_KEY, MotionEntity } from './NodeList';
 
 interface CheckInfo {
   event: 'check';
-  node: NodeInstance;
+  node: EventDataNode;
   checked: boolean;
   nativeEvent: MouseEvent;
   checkedNodes: DataNode[];
@@ -43,13 +52,13 @@ export interface TreeProps {
   prefixCls: string;
   className?: string;
   style?: React.CSSProperties;
+  focusable?: boolean;
   tabIndex?: number;
   children?: React.ReactNode;
   treeData?: DataNode[]; // Generate treeNode by children
   showLine?: boolean;
   showIcon?: boolean;
   icon?: IconType;
-  focusable?: boolean;
   selectable?: boolean;
   disabled?: boolean;
   multiple?: boolean;
@@ -65,12 +74,15 @@ export interface TreeProps {
   checkedKeys?: (Key)[] | { checked: (Key)[]; halfChecked: Key[] };
   defaultSelectedKeys?: Key[];
   selectedKeys?: Key[];
-  onClick?: (e: React.MouseEvent, treeNode: DataNode) => void;
-  onDoubleClick?: (e: React.MouseEvent, treeNode: DataNode) => void;
+  onFocus?: React.FocusEventHandler<HTMLDivElement>;
+  onBlur?: React.FocusEventHandler<HTMLDivElement>;
+  onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
+  onClick?: (e: React.MouseEvent, treeNode: EventDataNode) => void;
+  onDoubleClick?: (e: React.MouseEvent, treeNode: EventDataNode) => void;
   onExpand?: (
     expandedKeys: Key[],
     info: {
-      node: NodeInstance;
+      node: EventDataNode;
       expanded: boolean;
       nativeEvent: MouseEvent;
     },
@@ -81,7 +93,7 @@ export interface TreeProps {
     info: {
       event: 'select';
       selected: boolean;
-      node: NodeInstance;
+      node: EventDataNode;
       selectedNodes: DataNode[];
       nativeEvent: MouseEvent;
     },
@@ -90,32 +102,32 @@ export interface TreeProps {
     loadedKeys: Key[],
     info: {
       event: 'load';
-      node: NodeInstance;
+      node: EventDataNode;
     },
   ) => void;
-  loadData?: (treeNode: NodeInstance) => Promise<void>;
+  loadData?: (treeNode: EventDataNode) => Promise<void>;
   loadedKeys?: Key[];
-  onMouseEnter?: (info: { event: React.MouseEvent; node: NodeInstance }) => void;
-  onMouseLeave?: (info: { event: React.MouseEvent; node: NodeInstance }) => void;
-  onRightClick?: (info: { event: React.MouseEvent; node: NodeInstance }) => void;
-  onDragStart?: (info: { event: React.MouseEvent; node: NodeInstance }) => void;
+  onMouseEnter?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onMouseLeave?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onRightClick?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onDragStart?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
   onDragEnter?: (info: {
     event: React.MouseEvent;
-    node: NodeInstance;
+    node: EventDataNode;
     expandedKeys: Key[];
   }) => void;
-  onDragOver?: (info: { event: React.MouseEvent; node: NodeInstance }) => void;
-  onDragLeave?: (info: { event: React.MouseEvent; node: NodeInstance }) => void;
-  onDragEnd?: (info: { event: React.MouseEvent; node: NodeInstance }) => void;
+  onDragOver?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onDragLeave?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onDragEnd?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
   onDrop?: (info: {
     event: React.MouseEvent;
-    node: NodeInstance;
-    dragNode: NodeInstance;
+    node: EventDataNode;
+    dragNode: EventDataNode;
     dragNodesKeys: Key[];
     dropPosition: number;
     dropToGap: boolean;
   }) => void;
-  filterTreeNode?: (treeNode: NodeInstance) => boolean;
+  filterTreeNode?: (treeNode: EventDataNode) => boolean;
   motion?: any;
   switcherIcon?: IconType;
 
@@ -143,6 +155,7 @@ interface TreeState {
   flattenNodes: FlattenNode[];
 
   prevProps: TreeProps;
+  focused: boolean;
 }
 
 class Tree extends React.Component<TreeProps, TreeState> {
@@ -156,7 +169,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
     showLine: PropTypes.bool,
     showIcon: PropTypes.bool,
     icon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-    focusable: PropTypes.bool,
     selectable: PropTypes.bool,
     disabled: PropTypes.bool,
     multiple: PropTypes.bool,
@@ -235,6 +247,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
     treeData: [],
     flattenNodes: [],
 
+    focused: false,
+
     prevProps: null,
   };
 
@@ -349,7 +363,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return newState;
   }
 
-  onNodeDragStart = (event, node: NodeInstance) => {
+  onNodeDragStart = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
     const { eventKey } = node.props;
@@ -363,7 +377,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     });
 
     if (onDragStart) {
-      onDragStart({ event, node });
+      onDragStart({ event, node: convertNodePropsToEventData(node.props) });
     }
   };
 
@@ -374,7 +388,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
    * Better for use mouse move event to refresh drag state.
    * But let's just keep it to avoid event trigger logic change.
    */
-  onNodeDragEnter = (event, node) => {
+  onNodeDragEnter = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragEnter } = this.props;
     const { pos, eventKey } = node.props;
@@ -428,13 +442,17 @@ class Tree extends React.Component<TreeProps, TreeState> {
         }
 
         if (onDragEnter) {
-          onDragEnter({ event, node, expandedKeys: newExpandedKeys });
+          onDragEnter({
+            event,
+            node: convertNodePropsToEventData(node.props),
+            expandedKeys: newExpandedKeys,
+          });
         }
       }, 400);
     }, 0);
   };
 
-  onNodeDragOver = (event, node) => {
+  onNodeDragOver = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { onDragOver } = this.props;
     const { eventKey } = node.props;
 
@@ -450,11 +468,11 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
 
     if (onDragOver) {
-      onDragOver({ event, node });
+      onDragOver({ event, node: convertNodePropsToEventData(node.props) });
     }
   };
 
-  onNodeDragLeave = (event, node) => {
+  onNodeDragLeave = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { onDragLeave } = this.props;
 
     this.setState({
@@ -462,11 +480,11 @@ class Tree extends React.Component<TreeProps, TreeState> {
     });
 
     if (onDragLeave) {
-      onDragLeave({ event, node });
+      onDragLeave({ event, node: convertNodePropsToEventData(node.props) });
     }
   };
 
-  onNodeDragEnd = (event, node) => {
+  onNodeDragEnd = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { onDragEnd } = this.props;
     this.setState({
       dragOverNodeKey: '',
@@ -474,13 +492,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
     this.cleanDragState();
 
     if (onDragEnd) {
-      onDragEnd({ event, node });
+      onDragEnd({ event, node: convertNodePropsToEventData(node.props) });
     }
 
     this.dragNode = null;
   };
 
-  onNodeDrop = (event, node) => {
+  onNodeDrop = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { dragNodesKeys = [], dropPosition } = this.state;
     const { onDrop } = this.props;
     const { eventKey, pos } = node.props;
@@ -499,8 +517,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
     const dropResult = {
       event,
-      node,
-      dragNode: this.dragNode,
+      node: convertNodePropsToEventData(node.props),
+      dragNode: convertNodePropsToEventData(this.dragNode.props),
       dragNodesKeys: dragNodesKeys.slice(),
       dropPosition: dropPosition + Number(posArr[posArr.length - 1]),
       dropToGap: false,
@@ -526,40 +544,40 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeClick = (e, treeNode) => {
+  onNodeClick = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     const { onClick } = this.props;
     if (onClick) {
       onClick(e, treeNode);
     }
   };
 
-  onNodeDoubleClick = (e, treeNode) => {
+  onNodeDoubleClick = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     const { onDoubleClick } = this.props;
     if (onDoubleClick) {
       onDoubleClick(e, treeNode);
     }
   };
 
-  onNodeSelect = (e, treeNode) => {
+  onNodeSelect = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     let { selectedKeys } = this.state;
     const { keyEntities } = this.state;
     const { onSelect, multiple } = this.props;
-    const { selected, eventKey } = treeNode.props;
+    const { selected, key } = treeNode;
     const targetSelected = !selected;
 
     // Update selected keys
     if (!targetSelected) {
-      selectedKeys = arrDel(selectedKeys, eventKey);
+      selectedKeys = arrDel(selectedKeys, key);
     } else if (!multiple) {
-      selectedKeys = [eventKey];
+      selectedKeys = [key];
     } else {
-      selectedKeys = arrAdd(selectedKeys, eventKey);
+      selectedKeys = arrAdd(selectedKeys, key);
     }
 
     // [Legacy] Not found related usage in doc or upper libs
     const selectedNodes = selectedKeys
-      .map(key => {
-        const entity = keyEntities[key];
+      .map(selectedKey => {
+        const entity = keyEntities[selectedKey];
         if (!entity) return null;
 
         return entity.node;
@@ -579,16 +597,18 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeCheck = (e, treeNode, checked) => {
+  onNodeCheck = (
+    e: React.MouseEvent<HTMLDivElement>,
+    treeNode: EventDataNode,
+    checked: boolean,
+  ) => {
     const {
       keyEntities,
       checkedKeys: oriCheckedKeys,
       halfCheckedKeys: oriHalfCheckedKeys,
     } = this.state;
     const { checkStrictly, onCheck } = this.props;
-    const {
-      props: { eventKey },
-    } = treeNode;
+    const { key } = treeNode;
 
     // Prepare trigger arguments
     let checkedObj;
@@ -600,20 +620,18 @@ class Tree extends React.Component<TreeProps, TreeState> {
     };
 
     if (checkStrictly) {
-      const checkedKeys = checked
-        ? arrAdd(oriCheckedKeys, eventKey)
-        : arrDel(oriCheckedKeys, eventKey);
-      const halfCheckedKeys = arrDel(oriHalfCheckedKeys, eventKey);
+      const checkedKeys = checked ? arrAdd(oriCheckedKeys, key) : arrDel(oriCheckedKeys, key);
+      const halfCheckedKeys = arrDel(oriHalfCheckedKeys, key);
       checkedObj = { checked: checkedKeys, halfChecked: halfCheckedKeys };
 
       eventObj.checkedNodes = checkedKeys
-        .map(key => keyEntities[key])
+        .map(checkedKey => keyEntities[checkedKey])
         .filter(entity => entity)
         .map(entity => entity.node);
 
       this.setUncontrolledState({ checkedKeys });
     } else {
-      const { checkedKeys, halfCheckedKeys } = conductCheck([eventKey], checked, keyEntities, {
+      const { checkedKeys, halfCheckedKeys } = conductCheck([key], checked, keyEntities, {
         checkedKeys: oriCheckedKeys,
         halfCheckedKeys: oriHalfCheckedKeys,
       });
@@ -625,8 +643,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
       eventObj.checkedNodesPositions = [];
       eventObj.halfCheckedKeys = halfCheckedKeys;
 
-      checkedKeys.forEach(key => {
-        const entity = keyEntities[key];
+      checkedKeys.forEach(checkedKey => {
+        const entity = keyEntities[checkedKey];
         if (!entity) return;
 
         const { node, pos } = entity;
@@ -646,18 +664,14 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeLoad = treeNode =>
+  onNodeLoad = (treeNode: EventDataNode) =>
     new Promise(resolve => {
       // We need to get the latest state of loading/loaded keys
       this.setState(({ loadedKeys = [], loadingKeys = [] }): any => {
         const { loadData, onLoad } = this.props;
-        const { eventKey } = treeNode.props;
+        const { key } = treeNode;
 
-        if (
-          !loadData ||
-          loadedKeys.indexOf(eventKey) !== -1 ||
-          loadingKeys.indexOf(eventKey) !== -1
-        ) {
+        if (!loadData || loadedKeys.indexOf(key) !== -1 || loadingKeys.indexOf(key) !== -1) {
           // react 15 will warn if return null
           return {};
         }
@@ -666,8 +680,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
         const promise = loadData(treeNode);
         promise.then(() => {
           const { loadedKeys: currentLoadedKeys, loadingKeys: currentLoadingKeys } = this.state;
-          const newLoadedKeys = arrAdd(currentLoadedKeys, eventKey);
-          const newLoadingKeys = arrDel(currentLoadingKeys, eventKey);
+          const newLoadedKeys = arrAdd(currentLoadedKeys, key);
+          const newLoadingKeys = arrDel(currentLoadingKeys, key);
 
           // onLoad should trigger before internal setState to avoid `loadData` trigger twice.
           // https://github.com/ant-design/ant-design/issues/12464
@@ -689,19 +703,19 @@ class Tree extends React.Component<TreeProps, TreeState> {
         });
 
         return {
-          loadingKeys: arrAdd(loadingKeys, eventKey),
+          loadingKeys: arrAdd(loadingKeys, key),
         };
       });
     });
 
-  onNodeExpand = (e, treeNode) => {
+  onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     let { expandedKeys } = this.state;
     const { treeData } = this.state;
     const { onExpand, loadData } = this.props;
-    const { eventKey, expanded } = treeNode.props;
+    const { key, expanded } = treeNode;
 
     // Update selected keys
-    const index = expandedKeys.indexOf(eventKey);
+    const index = expandedKeys.indexOf(key);
     const targetExpanded = !expanded;
 
     warning(
@@ -710,9 +724,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
     );
 
     if (targetExpanded) {
-      expandedKeys = arrAdd(expandedKeys, eventKey);
+      expandedKeys = arrAdd(expandedKeys, key);
     } else {
-      expandedKeys = arrDel(expandedKeys, eventKey);
+      expandedKeys = arrDel(expandedKeys, key);
     }
 
     const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys);
@@ -741,21 +755,21 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return null;
   };
 
-  onNodeMouseEnter = (event, node) => {
+  onNodeMouseEnter = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
     const { onMouseEnter } = this.props;
     if (onMouseEnter) {
       onMouseEnter({ event, node });
     }
   };
 
-  onNodeMouseLeave = (event, node) => {
+  onNodeMouseLeave = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
     const { onMouseLeave } = this.props;
     if (onMouseLeave) {
       onMouseLeave({ event, node });
     }
   };
 
-  onNodeContextMenu = (event, node) => {
+  onNodeContextMenu = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
     const { onRightClick } = this.props;
     if (onRightClick) {
       event.preventDefault();
@@ -763,6 +777,22 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
+  onFocus: React.FocusEventHandler<HTMLDivElement> = (...args) => {
+    const { onFocus } = this.props;
+    this.setState({ focused: true });
+    if (onFocus) {
+      onFocus(...args);
+    }
+  };
+
+  onBlur: React.FocusEventHandler<HTMLDivElement> = (...args) => {
+    const { onBlur } = this.props;
+    this.setState({ focused: false });
+    if (onBlur) {
+      onBlur(...args);
+    }
+  };
+
   /**
    * Only update the value which is not in props
    */
@@ -784,6 +814,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   render() {
     const {
+      focused,
       flattenNodes,
       keyEntities,
       expandedKeys,
@@ -799,9 +830,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
     const {
       prefixCls,
       className,
-      focusable,
       style,
       showLine,
+      focusable,
       tabIndex = 0,
       selectable,
       showIcon,
@@ -816,12 +847,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
       filterTreeNode,
       height,
       itemHeight,
+      onKeyDown,
     } = this.props;
-    const domProps: React.HTMLAttributes<HTMLUListElement> = getDataAndAria(this.props);
-
-    if (focusable) {
-      domProps.tabIndex = tabIndex;
-    }
+    const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
 
     return (
       <TreeContext.Provider
@@ -860,10 +888,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
         <NodeList
           className={classNames(prefixCls, className, {
             [`${prefixCls}-show-line`]: showLine,
+            [`${prefixCls}-focused`]: focused,
           })}
           prefixCls={prefixCls}
           style={style}
           data={flattenNodes}
+          selectable={selectable}
+          checkable={!!checkable}
           keyEntities={keyEntities}
           expandedKeys={expandedKeys}
           selectedKeys={selectedKeys}
@@ -877,6 +908,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
           dragging={dragging}
           height={height}
           itemHeight={itemHeight}
+          focusable={focusable}
+          focused={focused}
+          tabIndex={tabIndex}
+          onFocus={this.onFocus}
+          onBlur={this.onBlur}
+          onKeyDown={onKeyDown}
+          {...domProps}
         />
       </TreeContext.Provider>
     );
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index be8aad2a..f32f68a2 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -7,6 +7,7 @@ import { TreeContext, TreeContextProps } from './contextTypes';
 import { getDataAndAria } from './util';
 import { IconType, Key, DataNode } from './interface';
 import Indent from './Indent';
+import { convertNodePropsToEventData } from './utils/treeUtil';
 
 const ICON_OPEN = 'open';
 const ICON_CLOSE = 'close';
@@ -36,6 +37,8 @@ export interface TreeNodeProps {
   data?: DataNode;
   isStart?: boolean[];
   isEnd?: boolean[];
+  active: boolean;
+  onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
 
   // By user
   isLeaf?: boolean;
@@ -107,7 +110,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const {
       context: { onNodeClick },
     } = this.props;
-    onNodeClick(e, this);
+    onNodeClick(e, convertNodePropsToEventData(this.props));
 
     if (this.isSelectable()) {
       this.onSelect(e);
@@ -120,7 +123,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const {
       context: { onNodeDoubleClick },
     } = this.props;
-    onNodeDoubleClick(e, this);
+    onNodeDoubleClick(e, convertNodePropsToEventData(this.props));
   };
 
   onSelect = e => {
@@ -130,7 +133,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       context: { onNodeSelect },
     } = this.props;
     e.preventDefault();
-    onNodeSelect(e, this);
+    onNodeSelect(e, convertNodePropsToEventData(this.props));
   };
 
   onCheck = e => {
@@ -145,28 +148,28 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
 
     e.preventDefault();
     const targetChecked = !checked;
-    onNodeCheck(e, this, targetChecked);
+    onNodeCheck(e, convertNodePropsToEventData(this.props), targetChecked);
   };
 
   onMouseEnter = e => {
     const {
       context: { onNodeMouseEnter },
     } = this.props;
-    onNodeMouseEnter(e, this);
+    onNodeMouseEnter(e, convertNodePropsToEventData(this.props));
   };
 
   onMouseLeave = e => {
     const {
       context: { onNodeMouseLeave },
     } = this.props;
-    onNodeMouseLeave(e, this);
+    onNodeMouseLeave(e, convertNodePropsToEventData(this.props));
   };
 
   onContextMenu = e => {
     const {
       context: { onNodeContextMenu },
     } = this.props;
-    onNodeContextMenu(e, this);
+    onNodeContextMenu(e, convertNodePropsToEventData(this.props));
   };
 
   onDragStart = e => {
@@ -244,11 +247,11 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
   };
 
   // Disabled item still can be switch
-  onExpand = e => {
+  onExpand: React.MouseEventHandler<HTMLDivElement> = e => {
     const {
       context: { onNodeExpand },
     } = this.props;
-    onNodeExpand(e, this);
+    onNodeExpand(e, convertNodePropsToEventData(this.props));
   };
 
   // Drag usage
@@ -325,7 +328,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       // We needn't reload data when has children in sync logic
       // It's only needed in node expanded
       if (!this.hasChildren() && !loaded) {
-        onNodeLoad(this);
+        onNodeLoad(convertNodePropsToEventData(this.props));
       }
     }
   };
@@ -499,6 +502,8 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       halfChecked,
       loading,
       domRef,
+      active,
+      onMouseMove,
       ...otherProps
     } = this.props;
     const {
@@ -518,21 +523,21 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           [`${prefixCls}-treenode-checkbox-indeterminate`]: halfChecked,
           [`${prefixCls}-treenode-selected`]: selected,
           [`${prefixCls}-treenode-loading`]: loading,
+          [`${prefixCls}-treenode-active`]: active,
 
           'drag-over': !disabled && dragOver,
           'drag-over-gap-top': !disabled && dragOverGapTop,
           'drag-over-gap-bottom': !disabled && dragOverGapBottom,
-          'filter-node': filterTreeNode && filterTreeNode(this),
+          'filter-node': filterTreeNode && filterTreeNode(convertNodePropsToEventData(this.props)),
         })}
         style={style}
-        role="treeitem"
         onDragEnter={draggable ? this.onDragEnter : undefined}
         onDragOver={draggable ? this.onDragOver : undefined}
         onDragLeave={draggable ? this.onDragLeave : undefined}
         onDrop={draggable ? this.onDrop : undefined}
         onDragEnd={draggable ? this.onDragEnd : undefined}
+        onMouseMove={onMouseMove}
         {...dataOrAriaAttributeProps}
-        tabIndex={0}
       >
         <Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
         {this.renderSwitcher()}
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 8a491cf2..312b45ab 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -3,10 +3,10 @@
  * When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
  */
 import * as React from 'react';
-import { IconType, Key, DataEntity, NodeInstance } from './interface';
-import { InternalTreeNodeProps } from './TreeNode';
+import { IconType, Key, DataEntity, EventDataNode, NodeInstance } from './interface';
 
-type NodeMouseEventHandler = (e: MouseEvent, node: React.Component<InternalTreeNodeProps>) => void;
+type NodeMouseEventHandler = (e: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => void;
+type NodeDragEventHandler = (e: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => void;
 
 export interface TreeContextProps {
   prefixCls: string;
@@ -20,24 +20,28 @@ export interface TreeContextProps {
   disabled: boolean;
   keyEntities: Record<Key, DataEntity>;
 
-  loadData: (treeNode: NodeInstance) => Promise<void>;
-  filterTreeNode: (treeNode: NodeInstance) => boolean;
+  loadData: (treeNode: EventDataNode) => Promise<void>;
+  filterTreeNode: (treeNode: EventDataNode) => boolean;
 
   onNodeClick: NodeMouseEventHandler;
   onNodeDoubleClick: NodeMouseEventHandler;
   onNodeExpand: NodeMouseEventHandler;
   onNodeSelect: NodeMouseEventHandler;
-  onNodeCheck: (e: MouseEvent, treeNode: NodeInstance, checked: boolean) => void;
-  onNodeLoad: (treeNode: NodeInstance) => void;
+  onNodeCheck: (
+    e: React.MouseEvent<HTMLDivElement>,
+    treeNode: EventDataNode,
+    checked: boolean,
+  ) => void;
+  onNodeLoad: (treeNode: EventDataNode) => void;
   onNodeMouseEnter: NodeMouseEventHandler;
   onNodeMouseLeave: NodeMouseEventHandler;
   onNodeContextMenu: NodeMouseEventHandler;
-  onNodeDragStart: NodeMouseEventHandler;
-  onNodeDragEnter: NodeMouseEventHandler;
-  onNodeDragOver: NodeMouseEventHandler;
-  onNodeDragLeave: NodeMouseEventHandler;
-  onNodeDragEnd: NodeMouseEventHandler;
-  onNodeDrop: NodeMouseEventHandler;
+  onNodeDragStart: NodeDragEventHandler;
+  onNodeDragEnter: NodeDragEventHandler;
+  onNodeDragOver: NodeDragEventHandler;
+  onNodeDragLeave: NodeDragEventHandler;
+  onNodeDragEnd: NodeDragEventHandler;
+  onNodeDrop: NodeDragEventHandler;
 }
 
 export const TreeContext: React.Context<TreeContextProps | null> = React.createContext(null);
diff --git a/src/interface.tsx b/src/interface.tsx
index d9df3b5f..b040c884 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -18,6 +18,20 @@ export interface DataNode {
   style?: React.CSSProperties;
 }
 
+export interface EventDataNode extends Omit<DataNode, 'children'> {
+  expanded: boolean;
+  selected: boolean;
+  checked: boolean;
+  loaded: boolean;
+  loading: boolean;
+  halfChecked: boolean;
+  dragOver: boolean;
+  dragOverGapTop: boolean;
+  dragOverGapBottom: boolean;
+  pos: string;
+  active: boolean;
+}
+
 export type IconType = React.ReactNode | ((props: TreeNodeProps) => React.ReactNode);
 
 export type Key = string | number;
@@ -29,7 +43,9 @@ export type NodeElement = React.ReactElement<TreeNodeProps> & {
   };
 };
 
-export type NodeInstance = React.Component<TreeNodeProps>;
+export type NodeInstance = React.Component<TreeNodeProps> & {
+  selectHandle?: HTMLSpanElement;
+};
 
 export interface Entity {
   node: NodeElement;
diff --git a/src/util.tsx b/src/util.tsx
index 7be4c954..d0e17afc 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -3,9 +3,9 @@
  */
 
 import React from 'react';
-import warning from 'warning';
+import warning from 'rc-util/lib/warning';
 import TreeNode, { TreeNodeProps } from './TreeNode';
-import { NodeElement, Key, DataNode, Entity, DataEntity } from './interface';
+import { NodeElement, Key, DataNode, Entity, DataEntity, NodeInstance } from './interface';
 import { TreeProps } from './Tree';
 
 const DRAG_SIDE_RANGE = 0.25;
@@ -62,7 +62,7 @@ export function getDragNodesKeys(dragNodeKey: Key, keyEntities: Record<Key, Data
 }
 
 // Only used when drag, not affect SSR.
-export function calcDropPosition(event: React.MouseEvent, treeNode: NodeElement) {
+export function calcDropPosition(event: React.MouseEvent, treeNode: NodeInstance) {
   const { clientY } = event;
   const { top, bottom, height } = treeNode.selectHandle.getBoundingClientRect();
   const des = Math.max(height * DRAG_SIDE_RANGE, DRAG_MIN_GAP);
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 3299d88d..a44f5248 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -1,8 +1,9 @@
 import * as React from 'react';
 import toArray from 'rc-util/lib/Children/toArray';
-import warning from 'warning';
-import { DataNode, FlattenNode, NodeElement, DataEntity, Key } from '../interface';
+import warning from 'rc-util/lib/warning';
+import { DataNode, FlattenNode, NodeElement, DataEntity, Key, EventDataNode } from '../interface';
 import { getPosition, isTreeNode } from '../util';
+import { TreeNodeProps } from '../TreeNode';
 
 export function getKey(key: Key, pos: string) {
   if (key !== null && key !== undefined) {
@@ -276,3 +277,49 @@ export function getTreeNodeProps(
 
   return treeNodeProps;
 }
+
+export function convertNodePropsToEventData(props: TreeNodeProps): EventDataNode {
+  const {
+    data,
+    expanded,
+    selected,
+    checked,
+    loaded,
+    loading,
+    halfChecked,
+    dragOver,
+    dragOverGapTop,
+    dragOverGapBottom,
+    pos,
+    active,
+  } = props;
+
+  const eventData = {
+    ...data,
+    expanded,
+    selected,
+    checked,
+    loaded,
+    loading,
+    halfChecked,
+    dragOver,
+    dragOverGapTop,
+    dragOverGapBottom,
+    pos,
+    active,
+  };
+
+  if (!('props' in eventData)) {
+    Object.defineProperty(eventData, 'props', {
+      get() {
+        warning(
+          false,
+          'Second param return from event is node data instead of TreeNode instance. Please read value directly instead of reading from `props`.',
+        );
+        return props;
+      },
+    });
+  }
+
+  return eventData;
+}
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
new file mode 100644
index 00000000..af4a9304
--- /dev/null
+++ b/tests/Accessibility.spec.js
@@ -0,0 +1,187 @@
+/* eslint-disable no-undef, react/no-multi-comp */
+import React from 'react';
+import KeyCode from 'rc-util/lib/KeyCode';
+import { mount } from 'enzyme';
+import Tree from '../src';
+import { InternalTreeNode } from '../src/TreeNode';
+import { spyConsole } from './util';
+
+describe('Tree Accessibility', () => {
+  spyConsole();
+
+  describe('key operation', () => {
+    function typeTest(props, spaceCallback, enterCallback) {
+      const onExpand = jest.fn();
+      const onFocus = jest.fn();
+      const onBlur = jest.fn();
+      const onKeyDown = jest.fn();
+
+      function checkKeyDownTrigger() {
+        expect(onKeyDown).toHaveBeenCalled();
+        onKeyDown.mockReset();
+      }
+
+      const wrapper = mount(
+        <Tree
+          {...props}
+          onExpand={onExpand}
+          onFocus={onFocus}
+          onBlur={onBlur}
+          onKeyDown={onKeyDown}
+          defaultExpandAll
+          treeData={[{ key: 'parent', children: [{ key: 'child 1' }, { key: 'child 2' }] }]}
+        />,
+      );
+
+      // Focus
+      wrapper.find('input').simulate('focus');
+      expect(onFocus).toHaveBeenCalled();
+
+      // Arrow up: last one
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.UP });
+      expect(
+        wrapper
+          .find(InternalTreeNode)
+          .find('.rc-tree-treenode')
+          .last()
+          .hasClass('rc-tree-treenode-active'),
+      ).toBeTruthy();
+      checkKeyDownTrigger();
+
+      // Arrow down: first one
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
+      expect(
+        wrapper
+          .find(InternalTreeNode)
+          .find('.rc-tree-treenode')
+          .first()
+          .hasClass('rc-tree-treenode-active'),
+      ).toBeTruthy();
+      checkKeyDownTrigger();
+
+      // Arrow up: last one again
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.UP });
+      expect(
+        wrapper
+          .find(InternalTreeNode)
+          .find('.rc-tree-treenode')
+          .last()
+          .hasClass('rc-tree-treenode-active'),
+      ).toBeTruthy();
+      checkKeyDownTrigger();
+
+      // Arrow left: parent
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.LEFT });
+      expect(
+        wrapper
+          .find(InternalTreeNode)
+          .find('.rc-tree-treenode')
+          .first()
+          .hasClass('rc-tree-treenode-active'),
+      ).toBeTruthy();
+      checkKeyDownTrigger();
+
+      // Arrow left: collapse
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.LEFT });
+      expect(
+        wrapper
+          .find(InternalTreeNode)
+          .find('.rc-tree-treenode')
+          .first()
+          .hasClass('rc-tree-treenode-active'),
+      ).toBeTruthy();
+      expect(onExpand).toHaveBeenCalledWith(['child 1', 'child 2'], expect.anything());
+      checkKeyDownTrigger();
+
+      // Arrow right: expand
+      onExpand.mockReset();
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.RIGHT });
+      expect(
+        wrapper
+          .find(InternalTreeNode)
+          .find('.rc-tree-treenode')
+          .first()
+          .hasClass('rc-tree-treenode-active'),
+      ).toBeTruthy();
+      expect(onExpand).toHaveBeenCalledWith(['child 1', 'child 2', 'parent'], expect.anything());
+      checkKeyDownTrigger();
+
+      // Arrow right: first child
+      onExpand.mockReset();
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.RIGHT });
+      expect(
+        wrapper
+          .find(InternalTreeNode)
+          .find('.rc-tree-treenode')
+          .at(1)
+          .hasClass('rc-tree-treenode-active'),
+      ).toBeTruthy();
+      checkKeyDownTrigger();
+
+      // SPACE: confirm
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.SPACE });
+      spaceCallback();
+      checkKeyDownTrigger();
+
+      // ENTER: confirm again
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.ENTER });
+      enterCallback();
+      checkKeyDownTrigger();
+
+      // Blur
+      wrapper.find('input').simulate('blur');
+      expect(onBlur).toHaveBeenCalled();
+    }
+
+    it('onSelect', () => {
+      const onSelect = jest.fn();
+      typeTest(
+        { onSelect },
+        () => {
+          expect(onSelect).toHaveBeenCalledWith(['child 1'], expect.anything());
+          onSelect.mockReset();
+        },
+        () => {
+          expect(onSelect).toHaveBeenCalledWith([], expect.anything());
+        },
+      );
+    });
+
+    it('onCheck', () => {
+      const onCheck = jest.fn();
+      typeTest(
+        { onCheck, checkable: true, selectable: false },
+        () => {
+          expect(onCheck).toHaveBeenCalledWith(['child 1'], expect.anything());
+          onCheck.mockReset();
+        },
+        () => {
+          expect(onCheck).toHaveBeenCalledWith([], expect.anything());
+        },
+      );
+    });
+
+    it('not crash if not exist', () => {
+      const wrapper = mount(<Tree defaultExpandAll treeData={[]} />);
+
+      wrapper.find('input').simulate('focus');
+
+      // Arrow should not work
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.UP });
+      expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeFalsy();
+    });
+
+    it('remove active if mouse hover', () => {
+      const wrapper = mount(<Tree defaultExpandAll treeData={[{ key: 'parent' }]} />);
+
+      wrapper.find('input').simulate('focus');
+
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.UP });
+      expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeTruthy();
+
+      // Mouse move
+      wrapper.find('.rc-tree-treenode').simulate('mouseMove');
+      expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeFalsy();
+    });
+  });
+});
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 022d0f02..632c3ce0 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -6,6 +6,8 @@ import { renderToJson } from 'enzyme-to-json';
 import Tree, { TreeNode } from '../src';
 import { InternalTreeNode } from '../src/TreeNode';
 import { objectMatcher, spyConsole, spyError } from './util';
+import { convertNodePropsToEventData } from '../src/utils/treeUtil';
+import { resetWarned } from 'rc-util/lib/warning';
 
 const OPEN_CLASSNAME = '.rc-tree-switcher_open';
 const CHECKED_CLASSNAME = '.rc-tree-checkbox-checked';
@@ -156,20 +158,31 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       const switcher = wrapper.find('.rc-tree-switcher');
-      const node = wrapper.find(InternalTreeNode).instance();
 
+      let node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       switcher.simulate('click');
       expect(handleExpand).toHaveBeenCalledWith(['0-0'], {
         expanded: true,
         node,
-        nativeEvent: expect.objectContaining({}),
+        nativeEvent: expect.anything(),
       });
 
+      node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       switcher.simulate('click');
       expect(handleExpand).toHaveBeenCalledWith([], {
         expanded: false,
         node,
-        nativeEvent: expect.objectContaining({}),
+        nativeEvent: expect.anything(),
       });
     });
   });
@@ -312,19 +325,20 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      const treeNode1 = wrapper.find(InternalTreeNode).first();
-      const treeNode2 = wrapper.find(InternalTreeNode).last();
-      // const treeElm1 = wrapper.find(Tree).props().children;
-      // const treeElm2 = treeNode1.props().children;
 
       const nodeData00 = { title: 'parent 1', key: '0-0' };
       const nodeData000 = { title: 'leaf 1', key: '0-0-0' };
 
+      let node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       wrapper
         .find('.rc-tree-checkbox')
         .first()
         .simulate('click');
-
       expect(handleCheck).toHaveBeenCalledWith(
         ['0-0', '0-0-0'],
         objectMatcher({
@@ -336,11 +350,17 @@ describe('Tree Basic', () => {
           ],
           event: 'check',
           halfCheckedKeys: [],
-          node: treeNode1.instance(),
+          node,
           nativeEvent: {},
         }),
       );
 
+      node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .last()
+          .props(),
+      );
       wrapper
         .find('.rc-tree-checkbox')
         .last()
@@ -353,7 +373,7 @@ describe('Tree Basic', () => {
           checkedNodesPositions: [],
           event: 'check',
           halfCheckedKeys: [],
-          node: treeNode2.instance(),
+          node,
           nativeEvent: {},
         }),
       );
@@ -777,12 +797,13 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       const nodeContent = wrapper.find('.rc-tree-node-content-wrapper');
-      const node = wrapper
-        .find(InternalTreeNode)
-        .first()
-        .instance();
-      const nodeElm = wrapper.find(Tree).props().children;
 
+      let node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       nodeContent.simulate('click');
       expect(handleSelect).toHaveBeenCalledWith(
         ['0-0'],
@@ -795,6 +816,12 @@ describe('Tree Basic', () => {
         }),
       );
 
+      node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       nodeContent.simulate('click');
       expect(handleSelect).toHaveBeenCalledWith(
         [],
@@ -820,12 +847,16 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      const treeNode1 = wrapper.find(InternalTreeNode).first();
-      const treeNode2 = wrapper.find(InternalTreeNode).last();
 
       const dataNode1 = { title: 'parent 1', key: '0-0' };
       const dataNode2 = { title: 'leaf 1', key: '0-0-0' };
 
+      let node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       wrapper
         .find('.rc-tree-node-content-wrapper')
         .first()
@@ -841,11 +872,17 @@ describe('Tree Basic', () => {
           ],
           event: 'check',
           halfCheckedKeys: [],
-          node: treeNode1.instance(),
+          node,
           nativeEvent: {},
         }),
       );
 
+      node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .last()
+          .props(),
+      );
       wrapper
         .find('.rc-tree-node-content-wrapper')
         .last()
@@ -858,7 +895,7 @@ describe('Tree Basic', () => {
           checkedNodesPositions: [],
           event: 'check',
           halfCheckedKeys: [],
-          node: treeNode2.instance(),
+          node,
           nativeEvent: {},
         }),
       );
@@ -874,8 +911,9 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
     wrapper.find('.rc-tree-node-content-wrapper').simulate('contextMenu');
-    expect(handleRightClick.mock.calls[0][0].node).toBe(wrapper.find(InternalTreeNode).instance());
+    expect(handleRightClick.mock.calls[0][0].node).toEqual(node);
   });
 
   it('fires rightClick should not change selected item', () => {
@@ -900,8 +938,9 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
     wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseEnter');
-    expect(handleMouseEnter.mock.calls[0][0].node).toBe(wrapper.find(InternalTreeNode).instance());
+    expect(handleMouseEnter.mock.calls[0][0].node).toEqual(node);
   });
 
   it('fires mouseLeave events', () => {
@@ -913,13 +952,14 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
     wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseLeave');
-    expect(handleMouseLeave.mock.calls[0][0].node).toBe(wrapper.find(InternalTreeNode).instance());
+    expect(handleMouseLeave.mock.calls[0][0].node).toEqual(node);
   });
 
   it('filters nodes', () => {
     function filterTreeNode(treeNode) {
-      return treeNode.props.title.indexOf('parent') > -1;
+      return treeNode.title.indexOf('parent') > -1;
     }
     const wrapper = mount(
       <Tree filterTreeNode={filterTreeNode}>
@@ -949,8 +989,9 @@ describe('Tree Basic', () => {
         <TreeNode title="parent 1" key="0-0" />
       </Tree>,
     );
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
     wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(loadData).toHaveBeenCalledWith(wrapper.find(InternalTreeNode).instance());
+    expect(loadData).toHaveBeenCalledWith(node);
     expect(then).toHaveBeenCalled();
   });
 
@@ -970,19 +1011,21 @@ describe('Tree Basic', () => {
       const onDragStart = jest.fn();
       const wrapper = mount(createTree({ onDragStart }));
       const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
-      treeNode.simulate('dragStart');
-      const event = onDragStart.mock.calls[0][0];
-      expect(event.node).toBe(
+      const node = convertNodePropsToEventData(
         wrapper
           .find(InternalTreeNode)
           .at(1)
-          .instance(),
+          .props(),
       );
+      treeNode.simulate('dragStart');
+      const event = onDragStart.mock.calls[0][0];
+      expect(event.node).toEqual(node);
     });
 
     it('fires dragEnter event', done => {
       const onDragEnter = jest.fn();
       const wrapper = mount(createTree({ onDragEnter }));
+
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
       wrapper
         .find('.dropTarget')
@@ -990,13 +1033,15 @@ describe('Tree Basic', () => {
         .simulate('dragEnter');
       expect(onDragEnter).not.toHaveBeenCalled();
       setTimeout(() => {
-        const event = onDragEnter.mock.calls[0][0];
-        expect(event.node).toBe(
+        wrapper.update();
+        const node = convertNodePropsToEventData(
           wrapper
             .find(InternalTreeNode)
             .at(2)
-            .instance(),
+            .props(),
         );
+        const event = onDragEnter.mock.calls[0][0];
+        expect(event.node).toEqual(node);
         expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
         done();
       }, 500);
@@ -1005,70 +1050,76 @@ describe('Tree Basic', () => {
     it('fires dragOver event', () => {
       const onDragOver = jest.fn();
       const wrapper = mount(createTree({ onDragOver }));
+      const node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(2)
+          .props(),
+      );
       wrapper
         .find('.dropTarget')
         .at(0)
         .simulate('dragOver');
+
       const event = onDragOver.mock.calls[0][0];
-      expect(event.node).toBe(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .instance(),
-      );
+      expect(event.node).toEqual(node);
     });
 
     it('fires dragLeave event', () => {
       const onDragLeave = jest.fn();
       const wrapper = mount(createTree({ onDragLeave }));
+      const node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(2)
+          .props(),
+      );
       wrapper
         .find('.dropTarget')
         .at(0)
         .simulate('dragLeave');
       const event = onDragLeave.mock.calls[0][0];
-      expect(event.node).toBe(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .instance(),
-      );
+      expect(event.node).toEqual(node);
     });
 
     it('fires drop event', () => {
       const onDrop = jest.fn();
       const wrapper = mount(createTree({ onDrop }));
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      wrapper
-        .find('.dropTarget')
-        .at(0)
-        .simulate('drop');
-      const event = onDrop.mock.calls[0][0];
-      expect(event.node).toBe(
+      const dropNode = convertNodePropsToEventData(
         wrapper
           .find(InternalTreeNode)
           .at(2)
-          .instance(),
+          .props(),
       );
-      expect(event.dragNode).toBe(
+      const dragNode = convertNodePropsToEventData(
         wrapper
           .find(InternalTreeNode)
           .at(1)
-          .instance(),
+          .props(),
       );
+      wrapper
+        .find('.dropTarget')
+        .at(0)
+        .simulate('drop');
+      const event = onDrop.mock.calls[0][0];
+      expect(event.node).toEqual(dropNode);
+      expect(event.dragNode).toEqual(dragNode);
       expect(event.dragNodesKeys).toEqual(['0-0-0-0']);
     });
 
     it('fires dropEnd event', () => {
       const onDragEnd = jest.fn();
       const wrapper = mount(createTree({ onDragEnd }));
-      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
-      const event = onDragEnd.mock.calls[0][0];
-      expect(event.node).toBe(
+      const node = convertNodePropsToEventData(
         wrapper
           .find(InternalTreeNode)
           .at(1)
-          .instance(),
+          .props(),
       );
+      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
+      const event = onDragEnd.mock.calls[0][0];
+      expect(event.node).toEqual(node);
     });
 
     it('do not throw error when drag into another non-drag-able tree', () => {
@@ -1208,6 +1259,7 @@ describe('Tree Basic', () => {
     });
 
     it('Sub TreeNode', () => {
+      resetWarned();
       const wrapper = mount(
         <Tree defaultExpandAll>
           <TreeNode key="00" title="00" />
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index a691ac93..3304d69b 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -6,6 +6,8 @@ import PropTypes from 'prop-types';
 import Tree, { TreeNode } from '../src';
 import { InternalTreeNode } from '../src/TreeNode';
 import { objectMatcher, spyConsole, spyError } from './util';
+import { convertNodePropsToEventData } from '../src/utils/treeUtil';
+import { resetWarned } from 'rc-util/lib/warning';
 
 /**
  * For refactor purpose. All the props should be passed by test
@@ -119,11 +121,14 @@ describe('Tree Props', () => {
       expect(renderToJson(render(withSelectableBase))).toMatchSnapshot();
 
       const withSelectable = mount(withSelectableBase);
-      const parentNode = withSelectable.find(InternalTreeNode).first();
-      const targetNode = withSelectable.find(InternalTreeNode).last();
+      const getTargetNode = () => withSelectable.find(InternalTreeNode).last();
+      const getParentNode = () => withSelectable.find(InternalTreeNode).first();
 
       // Select leaf
-      targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
+      let node = convertNodePropsToEventData(getTargetNode().props());
+      getTargetNode()
+        .find('.rc-tree-node-content-wrapper')
+        .simulate('click');
 
       // traverseTreeNodes loops origin TreeNode and
       // onSelect trigger on `cloneElement` which is not the same instance
@@ -132,7 +137,7 @@ describe('Tree Props', () => {
         objectMatcher({
           event: 'select',
           selected: true,
-          node: targetNode.instance(),
+          node,
           selectedNodes: [{ key: '0-0-0' }],
           nativeEvent: {},
         }),
@@ -140,13 +145,16 @@ describe('Tree Props', () => {
       handleOnSelect.mockReset();
 
       // un-select leaf
-      targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
+      node = convertNodePropsToEventData(getTargetNode().props());
+      getTargetNode()
+        .find('.rc-tree-node-content-wrapper')
+        .simulate('click');
       expect(handleOnSelect).toHaveBeenCalledWith(
         [],
         objectMatcher({
           event: 'select',
           selected: false,
-          node: targetNode.instance(),
+          node,
           selectedNodes: [],
           nativeEvent: {},
         }),
@@ -154,20 +162,24 @@ describe('Tree Props', () => {
       handleOnSelect.mockReset();
 
       // Select leaf and then parent
-      targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
+      node = convertNodePropsToEventData(getTargetNode().props());
+      getTargetNode()
+        .find('.rc-tree-node-content-wrapper')
+        .simulate('click');
       expect(handleOnSelect).toHaveBeenCalledWith(
         ['0-0-0'],
         objectMatcher({
           event: 'select',
           selected: true,
-          node: targetNode.instance(),
+          node,
           selectedNodes: [{ key: '0-0-0' }],
           nativeEvent: {},
         }),
       );
       handleOnSelect.mockReset();
 
-      parentNode
+      node = convertNodePropsToEventData(getParentNode().props());
+      getParentNode()
         .find('.rc-tree-node-content-wrapper')
         .first()
         .simulate('click');
@@ -176,7 +188,7 @@ describe('Tree Props', () => {
         objectMatcher({
           event: 'select',
           selected: true,
-          node: parentNode.instance(),
+          node,
           selectedNodes: [{ key: '0-0' }],
           nativeEvent: {},
         }),
@@ -199,17 +211,20 @@ describe('Tree Props', () => {
     expect(renderToJson(render(multipleBase))).toMatchSnapshot();
 
     const wrapper = mount(multipleBase);
-    const parentNode = wrapper.find(InternalTreeNode).first();
-    const targetNode = wrapper.find(InternalTreeNode).last();
+    const getParentNode = () => wrapper.find(InternalTreeNode).first();
+    const getTargetNode = () => wrapper.find(InternalTreeNode).last();
 
     // Leaf select
-    targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
+    let node = convertNodePropsToEventData(getTargetNode().props());
+    getTargetNode()
+      .find('.rc-tree-node-content-wrapper')
+      .simulate('click');
     expect(handleOnSelect).toHaveBeenCalledWith(
       ['0-0-0'],
       objectMatcher({
         event: 'select',
         selected: true,
-        node: targetNode.instance(),
+        node,
         selectedNodes: [{ key: '0-0-0' }],
         nativeEvent: {},
       }),
@@ -217,7 +232,8 @@ describe('Tree Props', () => {
     handleOnSelect.mockReset();
 
     // Parent select
-    parentNode
+    node = convertNodePropsToEventData(getParentNode().props());
+    getParentNode()
       .find('.rc-tree-node-content-wrapper')
       .first()
       .simulate('click');
@@ -226,7 +242,7 @@ describe('Tree Props', () => {
       objectMatcher({
         event: 'select',
         selected: true,
-        node: parentNode.instance(),
+        node,
         selectedNodes: [{ key: '0-0-0' }, { key: '0-0' }],
         nativeEvent: {},
       }),
@@ -234,13 +250,16 @@ describe('Tree Props', () => {
     handleOnSelect.mockReset();
 
     // Leaf un-select
-    targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
+    node = convertNodePropsToEventData(getTargetNode().props());
+    getTargetNode()
+      .find('.rc-tree-node-content-wrapper')
+      .simulate('click');
     expect(handleOnSelect).toHaveBeenCalledWith(
       ['0-0'],
       objectMatcher({
         event: 'select',
         selected: false,
-        node: targetNode.instance(),
+        node,
         selectedNodes: [{ key: '0-0' }],
         nativeEvent: {},
       }),
@@ -269,16 +288,19 @@ describe('Tree Props', () => {
       expect(renderToJson(render(withCheckableBase))).toMatchSnapshot();
 
       const withCheckable = mount(withCheckableBase);
-      const targetNode = withCheckable.find(InternalTreeNode).last();
+      const getTargetNode = () => withCheckable.find(InternalTreeNode).last();
 
       // Click leaf
-      targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
+      let node = convertNodePropsToEventData(getTargetNode().props());
+      getTargetNode()
+        .find('.rc-tree-node-content-wrapper')
+        .simulate('click');
       expect(handleOnSelect).toHaveBeenCalledWith(
         ['0-0-0'],
         objectMatcher({
           event: 'select',
           selected: true,
-          node: targetNode.instance(),
+          node,
           selectedNodes: [{ key: '0-0-0' }],
           nativeEvent: {},
         }),
@@ -290,14 +312,17 @@ describe('Tree Props', () => {
       handleOnSelect.mockReset();
 
       // Click checkbox
-      targetNode.find('.rc-tree-checkbox').simulate('click');
+      node = convertNodePropsToEventData(getTargetNode().props());
+      getTargetNode()
+        .find('.rc-tree-checkbox')
+        .simulate('click');
 
       expect(handleOnCheck).toHaveBeenCalledWith(
         ['0-0-0', '0-0'],
         objectMatcher({
           event: 'check',
           checked: true,
-          node: targetNode.instance(),
+          node,
           checkedNodes: [{ key: '0-0-0' }, { key: '0-0' }],
           nativeEvent: {},
         }),
@@ -326,16 +351,19 @@ describe('Tree Props', () => {
       expect(renderToJson(render(withCheckableBase))).toMatchSnapshot();
 
       const withCheckable = mount(withCheckableBase);
-      const targetNode = withCheckable.find(InternalTreeNode).last();
+      const getTargetNode = () => withCheckable.find(InternalTreeNode).last();
 
       // Click leaf
-      targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
+      const node = convertNodePropsToEventData(getTargetNode().props());
+      getTargetNode()
+        .find('.rc-tree-node-content-wrapper')
+        .simulate('click');
       expect(handleOnCheck).toHaveBeenCalledWith(
         ['0-0-0', '0-0'],
         objectMatcher({
           event: 'check',
           checked: true,
-          node: targetNode.instance(),
+          node,
           checkedNodes: [{ key: '0-0-0' }, { key: '0-0' }],
           nativeEvent: {},
         }),
@@ -367,7 +395,7 @@ describe('Tree Props', () => {
     });
   });
 
-  // Don't crash
+  // // Don't crash
   describe('invalidate checkedKeys', () => {
     const errorSpy = spyError();
 
@@ -411,10 +439,13 @@ describe('Tree Props', () => {
 
     expect(wrapper.render()).toMatchSnapshot();
 
-    const targetNode = wrapper.find(InternalTreeNode).last();
+    const getTargetNode = () => wrapper.find(InternalTreeNode).last();
 
     // Click Leaf
-    targetNode.find('.rc-tree-checkbox').simulate('click');
+    const node = convertNodePropsToEventData(getTargetNode().props());
+    getTargetNode()
+      .find('.rc-tree-checkbox')
+      .simulate('click');
     expect(handleOnCheck).toHaveBeenCalledWith(
       {
         checked: ['0-0-0'],
@@ -423,7 +454,7 @@ describe('Tree Props', () => {
       objectMatcher({
         event: 'check',
         checked: true,
-        node: targetNode.instance(),
+        node,
         checkedNodes: [{ key: '0-0-0' }],
         nativeEvent: {},
       }),
@@ -450,6 +481,8 @@ describe('Tree Props', () => {
   // defaultSelectedKeys - is already full test in Tree.spec.js
 
   describe('loadData', () => {
+    const errorSpy = spyError();
+
     it('basic', () => {
       let called = 0;
 
@@ -484,7 +517,7 @@ describe('Tree Props', () => {
       expect(handleLoadData).not.toHaveBeenCalled();
 
       const switcher = wrapper.find('.rc-tree-switcher');
-      const node = wrapper.find(InternalTreeNode).instance();
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
       switcher.simulate('click');
 
       return timeoutPromise().then(() => {
@@ -498,7 +531,13 @@ describe('Tree Props', () => {
     it('with expandedKeys', () => {
       let called = 0;
       const keys = {};
+
+      resetWarned();
       const loadData = ({ props: { eventKey } }) => {
+        resetWarned();
+        expect(errorSpy()).toHaveBeenCalledWith(
+          'Warning: Second param return from event is node data instead of TreeNode instance. Please read value directly instead of reading from `props`.',
+        );
         keys[eventKey] = (keys[eventKey] || 0) + 1;
 
         return new Promise(() => {
@@ -671,9 +710,10 @@ describe('Tree Props', () => {
     const targetNode = parentNode.find(InternalTreeNode).last();
 
     // Select leaf
+    const node = convertNodePropsToEventData(targetNode.props());
     targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
 
-    expect(onClick).toHaveBeenCalledWith(expect.objectContaining({}), targetNode.instance());
+    expect(onClick).toHaveBeenCalledWith(expect.objectContaining({}), node);
   });
 
   it('onDoubleClick', () => {
@@ -692,10 +732,11 @@ describe('Tree Props', () => {
     const targetNode = parentNode.find(InternalTreeNode).last();
 
     // Select leaf
+    const node = convertNodePropsToEventData(targetNode.props());
     targetNode.find('.rc-tree-node-content-wrapper').simulate('doubleclick');
 
     expect(onClick).not.toHaveBeenCalled();
-    expect(onDoubleClick).toHaveBeenCalledWith(expect.objectContaining({}), targetNode.instance());
+    expect(onDoubleClick).toHaveBeenCalledWith(expect.objectContaining({}), node);
   });
 
   describe('loadedKeys & onLoad', () => {
@@ -739,11 +780,12 @@ describe('Tree Props', () => {
         </Tree>,
       );
       wrapper.setProps({ loadedKeys: [] });
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
       wrapper.find('.rc-tree-switcher').simulate('click');
-      expect(loadData).toHaveBeenCalledWith(wrapper.find(InternalTreeNode).instance());
+      expect(loadData).toHaveBeenCalledWith(node);
       expect(onLoad).toHaveBeenCalledWith(['0-0'], {
         event: 'load',
-        node: wrapper.find(InternalTreeNode).instance(),
+        node,
       });
     });
   });
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index aa998838..5aa2e202 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -1,613 +1,640 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`Tree Basic check basic render 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-checkbox"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox rc-tree-checkbox-disabled"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox rc-tree-checkbox-disabled"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Basic check check after data ready works 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-checkbox-checked"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-checkbox rc-tree-checkbox-checked"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="Light"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-checkbox-checked"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
+          />
+          <span
+            class="rc-tree-checkbox rc-tree-checkbox-checked"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="Light"
           >
-            Light
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              Light
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox rc-tree-checkbox-checked"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="Bamboo"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox rc-tree-checkbox-checked"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="Bamboo"
           >
-            Bamboo
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              Bamboo
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Basic check check update when Tree trigger componentWillReceiveProps 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox rc-tree-checkbox-checked"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="parent 1"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-checkbox rc-tree-checkbox-checked"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="parent 1"
           >
-            parent 1
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              parent 1
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="00"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="00"
           >
-            00
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              00
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="02"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="02"
           >
-            02
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              02
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="00"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="00"
           >
-            00
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              00
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="01"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="01"
           >
-            01
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              01
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="010"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="010"
           >
-            010
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              010
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="012"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="012"
           >
-            012
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              012
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Basic renders correctly 1`] = `
-<div
-  class="rc-tree-list rc-tree forTest rc-tree-show-line"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree forTest rc-tree-show-line"
+  >
+    <div>
       <div
-        class="spe rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="parent 1"
+        <div
+          class="spe rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
+          />
+          <span
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="parent 1"
           >
-            parent 1
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              parent 1
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-checkbox rc-tree-checkbox-disabled"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="leaf 1"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-checkbox rc-tree-checkbox-disabled"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="leaf 1"
           >
-            leaf 1
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              leaf 1
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="leaf"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="leaf"
           >
-            leaf
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              leaf
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="leaf"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="leaf"
           >
-            leaf
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              leaf
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox rc-tree-checkbox-disabled"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="leaf 2"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox rc-tree-checkbox-disabled"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="leaf 2"
           >
-            leaf 2
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              leaf 2
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Basic renders opaque children correctly 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_close"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
-          title="0"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__close"
+            class="rc-tree-switcher rc-tree-switcher_close"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
+            title="0"
           >
-            0
+            <span
+              class="rc-tree-iconEle rc-tree-icon__close"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              0
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index 36ae56b1..539ab9cf 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -1,778 +1,811 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`TreeNode Props className 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-          <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`TreeNode Props customize icon component 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__customize"
-          >
-            <span
-              class="cust-icon"
-            />
-          </span>
+            class="rc-tree-switcher rc-tree-switcher-noop"
+          />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__customize"
+            >
+              <span
+                class="cust-icon"
+              />
+            </span>
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`TreeNode Props customize icon element 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__customize"
-          >
-            <span
-              class="cust-icon"
-            />
-          </span>
+            class="rc-tree-switcher rc-tree-switcher-noop"
+          />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__customize"
+            >
+              <span
+                class="cust-icon"
+              />
+            </span>
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`TreeNode Props customize icon hide icon 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-title"
+            class="rc-tree-switcher rc-tree-switcher-noop"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`TreeNode Props data and aria props renders aria attributes on li 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        aria-label="0-0"
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          aria-label="0-0"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        aria-label="0-0-0"
-        class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          aria-label="0-0-0"
+          class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        aria-label="0-0-0-0"
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          aria-label="0-0-0-0"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-          <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        aria-label="0-0-1"
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          aria-label="0-0-1"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        aria-label="0-1"
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        </div>
+        <div
+          aria-label="0-1"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`TreeNode Props data and aria props renders data attributes on li 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        data-test="0-0"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          data-test="0-0"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
-        data-test="0-0-0"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
+          data-test="0-0-0"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        data-test="0-0-0-0"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          data-test="0-0-0-0"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-          <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        data-test="0-0-1"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          data-test="0-0-1"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        data-test="0-1"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          data-test="0-1"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`TreeNode Props isLeaf 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        <div
+          class="rc-tree-treenode"
         >
           <span
-            class="rc-tree-title"
+            class="rc-tree-switcher rc-tree-switcher-noop"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`TreeNode Props isLeaf 2`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        <div
+          class="rc-tree-treenode"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`TreeNode Props isLeaf 3`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index e6f9141e..7187eced 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -1,1744 +1,1869 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`Tree Props checkStrictly 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
+          />
+          <span
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props checkable default 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-checkbox"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props checkable without selectable 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
+          />
+          <span
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props custom switcher icon switcher icon 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
-          </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        >
-          <span>
-            switcherIconFromNode0-1
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
-          />
+            class="rc-tree-switcher rc-tree-switcher_open"
+          >
+            <span>
+              switcherIconFromNode0-1
+            </span>
+          </span>
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        >
-          <span>
-            switcherLeafIcon
-          </span>
-        </span>
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
-          />
-          <span
-            class="rc-tree-title"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           >
-            ---
+            <span>
+              switcherLeafIcon
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        >
-          <span>
-            switcherLeafIconFromNode0-1
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
+          >
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
-          />
+            class="rc-tree-switcher rc-tree-switcher-noop"
+          >
+            <span>
+              switcherLeafIconFromNode0-1
+            </span>
+          </span>
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        >
-          <span>
-            switcherLeafIcon
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
           </span>
-        </span>
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
-          <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
-          />
           <span
-            class="rc-tree-title"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           >
-            ---
+            <span>
+              switcherLeafIcon
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
-        >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        >
-          <span>
-            switcherLeafIconFromNode0-2-1
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
+          >
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
-          />
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-title"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           >
-            ---
+            <span>
+              switcherLeafIconFromNode0-2-1
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        >
-          <span>
-            switcherLeafIcon
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
+          >
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
-          />
+            class="rc-tree-switcher rc-tree-switcher-noop"
+          >
+            <span>
+              switcherLeafIcon
+            </span>
+          </span>
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props data and aria props renders aria attributes 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
     />
-  </div>
-</div>
+  </div>,
+  <div
+    aria-label="name"
+    class="rc-tree-list rc-tree"
+  >
+    <div>
+      <div
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
+      />
+    </div>
+  </div>,
+]
 `;
 
 exports[`Tree Props data and aria props renders data attributes 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
     />
-  </div>
-</div>
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+    data-test="tree"
+  >
+    <div>
+      <div
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
+      />
+    </div>
+  </div>,
+]
 `;
 
 exports[`Tree Props defaultExpandAll 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props disabled basic 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props icon 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__customize"
+            class="rc-tree-switcher rc-tree-switcher_open"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            <span>
-              ROOT ICON
+            <span
+              class="rc-tree-iconEle rc-tree-icon__customize"
+            >
+              <span>
+                ROOT ICON
+              </span>
+            </span>
+            <span
+              class="rc-tree-title"
+            >
+              ---
             </span>
           </span>
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+        >
           <span
-            class="rc-tree-title"
+            aria-hidden="true"
+            class="rc-tree-indent"
           >
-            ---
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
-        >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__customize"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            <span>
-              CUSTOMIZE ICON
+            <span
+              class="rc-tree-iconEle rc-tree-icon__customize"
+            >
+              <span>
+                CUSTOMIZE ICON
+              </span>
+            </span>
+            <span
+              class="rc-tree-title"
+            >
+              ---
             </span>
           </span>
-          <span
-            class="rc-tree-title"
-          >
-            ---
-          </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props invalidate checkedKeys null 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
+          />
+          <span
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props invalidate checkedKeys number 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-checkbox"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-checkbox"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-checkbox"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props loadData basic 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-title"
+            class="rc-tree-switcher rc-tree-switcher_open"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
+          <span
+            class="rc-tree-switcher rc-tree-switcher_close"
           />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher_close"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
-          title="---"
-        >
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props multiple 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props prefixCls 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
     />
-  </div>
-</div>
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
+      <div
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
+      />
+    </div>
+  </div>,
+]
 `;
 
 exports[`Tree Props prefixCls 2`] = `
-<div
-  class="test-prefix-list test-prefix"
-  role="tree"
->
-  <div>
-    <div
-      class="test-prefix-list-holder-inner"
-      style="display:flex;flex-direction:column"
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
     />
-  </div>
-</div>
+  </div>,
+  <div
+    class="test-prefix-list test-prefix"
+  >
+    <div>
+      <div
+        class="test-prefix-list-holder-inner"
+        style="display:flex;flex-direction:column"
+      />
+    </div>
+  </div>,
+]
 `;
 
 exports[`Tree Props selectable with selectable 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props selectable without selectable 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props showIcon 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_close"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__close"
+            class="rc-tree-switcher rc-tree-switcher_close"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__close"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props showIcon 2`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_close"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
-          title="---"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-title"
+            class="rc-tree-switcher rc-tree-switcher_close"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-title"
+            class="rc-tree-switcher rc-tree-switcher-noop"
+          />
+          <span
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props showIcon 3`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
       >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-          <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-close"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="---"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-close"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="---"
           >
-            ---
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              ---
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props showLine 1`] = `
-<div
-  class="rc-tree-list rc-tree rc-tree-show-line"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display:flex;flex-direction:column"
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      tabindex="0"
     />
-  </div>
-</div>
+  </div>,
+  <div
+    class="rc-tree-list rc-tree rc-tree-show-line"
+  >
+    <div>
+      <div
+        class="rc-tree-list-holder-inner"
+        style="display:flex;flex-direction:column"
+      />
+    </div>
+  </div>,
+]
 `;
 
 exports[`Tree Props treeData 1`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="T0"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="T0"
           >
-            T0
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              T0
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="T1"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="T1"
           >
-            T1
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              T1
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="T10"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="T10"
           >
-            T10
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              T10
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher_open"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-          title="T11"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__open"
+            class="rc-tree-switcher rc-tree-switcher_open"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+            title="T11"
           >
-            T11
+            <span
+              class="rc-tree-iconEle rc-tree-icon__open"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              T11
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit"
-          />
-          <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-start"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="T110"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit"
+            />
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="T110"
           >
-            T110
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              T110
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit"
-          />
-          <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="T111"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit"
+            />
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="T111"
           >
-            T111
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              T111
+            </span>
           </span>
-        </span>
-      </div>
-      <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
-      >
-        <span
-          aria-hidden="true"
-          class="rc-tree-indent"
+        </div>
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-indent-unit rc-tree-indent-unit-end"
-          />
-        </span>
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="T12"
-        >
+            aria-hidden="true"
+            class="rc-tree-indent"
+          >
+            <span
+              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+            />
+          </span>
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="T12"
           >
-            T12
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              T12
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;
 
 exports[`Tree Props treeData 2`] = `
-<div
-  class="rc-tree-list rc-tree"
-  role="tree"
->
-  <div>
-    <div
-      class="rc-tree-list-holder-inner"
-      style="display: flex; flex-direction: column;"
-    >
+Array [
+  <div
+    role="tree"
+  >
+    <input
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      tabindex="0"
+    />
+  </div>,
+  <div
+    class="rc-tree-list rc-tree"
+  >
+    <div>
       <div
-        class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        role="treeitem"
-        tabindex="0"
+        class="rc-tree-list-holder-inner"
+        style="display: flex; flex-direction: column;"
       >
-        <span
-          class="rc-tree-switcher rc-tree-switcher-noop"
-        />
-        <span
-          class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-          title="T0"
+        <div
+          class="rc-tree-treenode rc-tree-treenode-switcher-open"
         >
           <span
-            class="rc-tree-iconEle rc-tree-icon__docu"
+            class="rc-tree-switcher rc-tree-switcher-noop"
           />
           <span
-            class="rc-tree-title"
+            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+            title="T0"
           >
-            T0
+            <span
+              class="rc-tree-iconEle rc-tree-icon__docu"
+            />
+            <span
+              class="rc-tree-title"
+            >
+              T0
+            </span>
           </span>
-        </span>
+        </div>
       </div>
     </div>
-  </div>
-</div>
+  </div>,
+]
 `;

From 303bbe216f37e70e669b291bf3158e6b9b737c7d Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 17 Sep 2019 17:31:25 +0800
Subject: [PATCH 012/397] v3.0.0-alpha.20

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 83a23c36..4524b81a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.19",
+  "version": "3.0.0-alpha.20",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 088f0f4bfbb98a6388f2c020c0a13aea3412bdcb Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 17 Sep 2019 20:37:07 +0800
Subject: [PATCH 013/397] feat: Add addtional class for tree with active key

---
 assets/index.less |  2 +-
 src/NodeList.tsx  |  9 ++++++++-
 src/Tree.tsx      | 12 +++++++++++-
 3 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/assets/index.less b/assets/index.less
index 6af0112e..b7b778d3 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -5,7 +5,7 @@
   margin: 0;
   border: 1px solid transparent;
   
-  &-focused {
+  &-focused:not(&-active-focused) {
     border-color: cyan;
   }
 
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 4814a258..e0eba6b8 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -68,6 +68,7 @@ interface NodeListProps {
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
+  onActiveChange: (key: Key) => void;
 }
 
 /**
@@ -133,6 +134,7 @@ const NodeList: React.FC<NodeListProps> = ({
   onKeyDown,
   onFocus,
   onBlur,
+  onActiveChange,
 
   ...domProps
 }) => {
@@ -221,7 +223,12 @@ const NodeList: React.FC<NodeListProps> = ({
   };
 
   // =========================== Accessibility ==========================
-  const [activeKey, setActiveKey] = React.useState<Key>(null);
+  const [activeKey, setInnerActiveKey] = React.useState<Key>(null);
+
+  function setActiveKey(key: Key) {
+    setInnerActiveKey(key);
+    onActiveChange(key);
+  }
 
   const activeItem = data.find(({ data: { key } }) => key === activeKey);
 
diff --git a/src/Tree.tsx b/src/Tree.tsx
index a221edb1..c24f29f4 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -154,8 +154,10 @@ interface TreeState {
   treeData: DataNode[];
   flattenNodes: FlattenNode[];
 
-  prevProps: TreeProps;
   focused: boolean;
+  activeKey: Key;
+
+  prevProps: TreeProps;
 }
 
 class Tree extends React.Component<TreeProps, TreeState> {
@@ -248,6 +250,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     flattenNodes: [],
 
     focused: false,
+    activeKey: null,
 
     prevProps: null,
   };
@@ -793,6 +796,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
+  onActiveChange = (activeKey: Key) => {
+    this.setState({ activeKey });
+  };
+
   /**
    * Only update the value which is not in props
    */
@@ -826,6 +833,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       dragging,
       dragOverNodeKey,
       dropPosition,
+      activeKey,
     } = this.state;
     const {
       prefixCls,
@@ -889,6 +897,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
           className={classNames(prefixCls, className, {
             [`${prefixCls}-show-line`]: showLine,
             [`${prefixCls}-focused`]: focused,
+            [`${prefixCls}-active-focused`]: activeKey !== null,
           })}
           prefixCls={prefixCls}
           style={style}
@@ -914,6 +923,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
           onFocus={this.onFocus}
           onBlur={this.onBlur}
           onKeyDown={onKeyDown}
+          onActiveChange={this.onActiveChange}
           {...domProps}
         />
       </TreeContext.Provider>

From 628c4719eeab0c3aac74e81daa642d98a8446830 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 17 Sep 2019 20:37:42 +0800
Subject: [PATCH 014/397] v3.0.0-alpha.21

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4524b81a..3db23b6b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.20",
+  "version": "3.0.0-alpha.21",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 2de67d5f9b9d3d988cadbfd8661fa6313ef2ef8a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 17 Sep 2019 20:45:27 +0800
Subject: [PATCH 015/397] fix: Disabled should also work on accessibility

---
 src/NodeList.tsx                           | 4 +++-
 src/Tree.tsx                               | 1 +
 tests/Accessibility.spec.js                | 5 +++++
 tests/__snapshots__/TreeProps.spec.js.snap | 2 ++
 4 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index e0eba6b8..4fe47a8c 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -48,6 +48,7 @@ interface NodeListProps {
   tabIndex: number;
   checkable?: boolean;
   selectable?: boolean;
+  disabled?: boolean;
 
   expandedKeys: Key[];
   selectedKeys: Key[];
@@ -118,6 +119,7 @@ const NodeList: React.FC<NodeListProps> = ({
   loadingKeys,
   halfCheckedKeys,
   keyEntities,
+  disabled,
 
   dragging,
   dragOverNodeKey,
@@ -351,7 +353,7 @@ const NodeList: React.FC<NodeListProps> = ({
       <div role="tree">
         <input
           style={HIDDEN_STYLE}
-          disabled={focusable === false}
+          disabled={focusable === false || disabled}
           tabIndex={focusable !== false ? tabIndex : null}
           onKeyDown={onInternalKeyDown}
           onFocus={onFocus}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index c24f29f4..de186577 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -902,6 +902,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
           prefixCls={prefixCls}
           style={style}
           data={flattenNodes}
+          disabled={disabled}
           selectable={selectable}
           checkable={!!checkable}
           keyEntities={keyEntities}
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index af4a9304..47bd6133 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -184,4 +184,9 @@ describe('Tree Accessibility', () => {
       expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeFalsy();
     });
   });
+
+  it('disabled should prevent keyboard', () => {
+    const wrapper = mount(<Tree disabled />);
+    expect(wrapper.find('input').props().disabled).toBeTruthy();
+  });
 });
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index 7187eced..54ee323c 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -664,6 +664,7 @@ Array [
     role="tree"
   >
     <input
+      disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
     />
@@ -708,6 +709,7 @@ Array [
     role="tree"
   >
     <input
+      disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
     />

From a709679d7972717e12a1c0a06b75dd9862c57564 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 17 Sep 2019 20:51:41 +0800
Subject: [PATCH 016/397] v3.0.0-alpha.22

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 3db23b6b..95c711c2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.21",
+  "version": "3.0.0-alpha.22",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 6cedc8f20a02c80f665a202139a32410f2c4875e Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Tue, 17 Sep 2019 21:18:29 +0000
Subject: [PATCH 017/397] chore(deps-dev): update cross-env requirement from
 ^5.2.0 to ^6.0.0

Updates the requirements on [cross-env](https://github.com/kentcdodds/cross-env) to permit the latest version.
- [Release notes](https://github.com/kentcdodds/cross-env/releases)
- [Changelog](https://github.com/kentcdodds/cross-env/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kentcdodds/cross-env/compare/v5.2.0...v6.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 95c711c2..ee857cf8 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
     "@types/react": "^16.8.19",
     "@types/react-dom": "^16.8.4",
     "@types/warning": "^3.0.0",
-    "cross-env": "^5.2.0",
+    "cross-env": "^6.0.0",
     "css-animation": "^1.2.0",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",

From 9b6581dc7b70014a1d83fa100df1c213c4d05298 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 10:29:20 +0800
Subject: [PATCH 018/397] chore: Not real change value of hidden input

---
 src/NodeList.tsx                              |  1 +
 tests/__snapshots__/Tree.spec.js.snap         |  7 ++++++
 .../__snapshots__/TreeNodeProps.spec.js.snap  |  9 +++++++
 tests/__snapshots__/TreeProps.spec.js.snap    | 25 +++++++++++++++++++
 4 files changed, 42 insertions(+)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 4fe47a8c..267a5df0 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -358,6 +358,7 @@ const NodeList: React.FC<NodeListProps> = ({
           onKeyDown={onInternalKeyDown}
           onFocus={onFocus}
           onBlur={onBlur}
+          value=""
         />
       </div>
 
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index 5aa2e202..453141f1 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -8,6 +8,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -117,6 +118,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -195,6 +197,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -242,6 +245,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -306,6 +310,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -426,6 +431,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -603,6 +609,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index 539ab9cf..c95cb9b4 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -8,6 +8,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -159,6 +160,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -207,6 +209,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -255,6 +258,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -296,6 +300,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -452,6 +457,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -608,6 +614,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -674,6 +681,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -746,6 +754,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index 54ee323c..dcdbf6c9 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -8,6 +8,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -86,6 +87,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -164,6 +166,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -242,6 +245,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -366,6 +370,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -546,6 +551,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -570,6 +576,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -594,6 +601,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -667,6 +675,7 @@ Array [
       disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -712,6 +721,7 @@ Array [
       disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -756,6 +766,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -836,6 +847,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -914,6 +926,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -992,6 +1005,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1058,6 +1072,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1130,6 +1145,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1153,6 +1169,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1176,6 +1193,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1248,6 +1266,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1320,6 +1339,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1384,6 +1404,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1442,6 +1463,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1601,6 +1623,7 @@ Array [
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1624,6 +1647,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div
@@ -1834,6 +1858,7 @@ Array [
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
       tabindex="0"
+      value=""
     />
   </div>,
   <div

From 7e7d4546d9020829963db5922191dc4745d96abd Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 10:30:53 +0800
Subject: [PATCH 019/397] v3.0.0-alpha.23

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 95c711c2..327148d2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.22",
+  "version": "3.0.0-alpha.23",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 9e72b66b66f0f0cb3a84b8e594d3fb649555c307 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 10:45:11 +0800
Subject: [PATCH 020/397] chore: update hidden input style

---
 src/NodeList.tsx                              | 11 +++-
 tests/__snapshots__/Tree.spec.js.snap         | 14 +++---
 .../__snapshots__/TreeNodeProps.spec.js.snap  | 18 +++----
 tests/__snapshots__/TreeProps.spec.js.snap    | 50 +++++++++----------
 4 files changed, 51 insertions(+), 42 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 267a5df0..699a7bae 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -11,7 +11,16 @@ import { TreeContext } from './contextTypes';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
 import { getTreeNodeProps, getKey, convertNodePropsToEventData } from './utils/treeUtil';
 
-const HIDDEN_STYLE = { width: 0, height: 0, display: 'flex', overflow: 'hidden', opacity: 0 };
+const HIDDEN_STYLE = {
+  width: 0,
+  height: 0,
+  display: 'flex',
+  overflow: 'hidden',
+  opacity: 0,
+  border: 0,
+  padding: 0,
+  margin: 0,
+};
 
 export const MOTION_KEY = `RC_TREE_MOTION_${Math.random()}`;
 
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index 453141f1..4e3b86d5 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -6,7 +6,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -116,7 +116,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -195,7 +195,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -243,7 +243,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -308,7 +308,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -429,7 +429,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -607,7 +607,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index c95cb9b4..5e6e8b90 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -6,7 +6,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -158,7 +158,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -207,7 +207,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -256,7 +256,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -298,7 +298,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -455,7 +455,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -612,7 +612,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -679,7 +679,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -752,7 +752,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index dcdbf6c9..b0b2c482 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -6,7 +6,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -85,7 +85,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -164,7 +164,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -243,7 +243,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -368,7 +368,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -549,7 +549,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -574,7 +574,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -599,7 +599,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -673,7 +673,7 @@ Array [
   >
     <input
       disabled=""
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -719,7 +719,7 @@ Array [
   >
     <input
       disabled=""
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -764,7 +764,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -845,7 +845,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -924,7 +924,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1003,7 +1003,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1070,7 +1070,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -1143,7 +1143,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -1167,7 +1167,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -1191,7 +1191,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -1264,7 +1264,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -1337,7 +1337,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -1402,7 +1402,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -1461,7 +1461,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -1621,7 +1621,7 @@ Array [
     role="tree"
   >
     <input
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0"
+      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
     />
@@ -1645,7 +1645,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1856,7 +1856,7 @@ Array [
     role="tree"
   >
     <input
-      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0;"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />

From 0205a6d03b136daa237339bbfbf2aa1c29609a08 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 10:47:14 +0800
Subject: [PATCH 021/397] v3.0.0-alpha.24

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 58c177f4..2200a519 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.23",
+  "version": "3.0.0-alpha.24",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 15ff1e30bc56cb08da8393e0341e16dcc92ad5a6 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 11:12:39 +0800
Subject: [PATCH 022/397] feat: Add `activeKey` for control

---
 src/NodeList.tsx            | 78 +++++++++++++++++++------------------
 src/Tree.tsx                |  7 ++++
 tests/Accessibility.spec.js | 30 ++++++++++++++
 3 files changed, 78 insertions(+), 37 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 699a7bae..92790f98 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -22,6 +22,8 @@ const HIDDEN_STYLE = {
   margin: 0,
 };
 
+const noop = () => {};
+
 export const MOTION_KEY = `RC_TREE_MOTION_${Math.random()}`;
 
 const MotionNode: DataNode = {
@@ -53,6 +55,7 @@ interface NodeListProps {
   data: FlattenNode[];
   motion: any;
   focusable?: boolean;
+  activeKey?: Key;
   focused?: boolean;
   tabIndex: number;
   checkable?: boolean;
@@ -116,39 +119,42 @@ function getAccessibilityPath(item: FlattenNode): string {
   return path;
 }
 
-const NodeList: React.FC<NodeListProps> = ({
-  prefixCls,
-  data,
-  selectable,
-  checkable,
-  expandedKeys,
-  selectedKeys,
-  checkedKeys,
-  loadedKeys,
-  loadingKeys,
-  halfCheckedKeys,
-  keyEntities,
-  disabled,
-
-  dragging,
-  dragOverNodeKey,
-  dropPosition,
-  motion,
-
-  height,
-  itemHeight,
-
-  focusable,
-  focused,
-  tabIndex,
-
-  onKeyDown,
-  onFocus,
-  onBlur,
-  onActiveChange,
-
-  ...domProps
-}) => {
+const NodeList: React.FC<NodeListProps> = props => {
+  const {
+    prefixCls,
+    data,
+    selectable,
+    checkable,
+    expandedKeys,
+    selectedKeys,
+    checkedKeys,
+    loadedKeys,
+    loadingKeys,
+    halfCheckedKeys,
+    keyEntities,
+    disabled,
+
+    dragging,
+    dragOverNodeKey,
+    dropPosition,
+    motion,
+
+    height,
+    itemHeight,
+
+    focusable,
+    activeKey,
+    focused,
+    tabIndex,
+
+    onKeyDown,
+    onFocus,
+    onBlur,
+    onActiveChange,
+
+    ...domProps
+  } = props;
+
   // ============================== Motion ==============================
   const [disableVirtual, setDisableVirtual] = React.useState(false);
   const [prevExpandedKeys, setPrevExpandedKeys] = React.useState(expandedKeys);
@@ -234,10 +240,7 @@ const NodeList: React.FC<NodeListProps> = ({
   };
 
   // =========================== Accessibility ==========================
-  const [activeKey, setInnerActiveKey] = React.useState<Key>(null);
-
   function setActiveKey(key: Key) {
-    setInnerActiveKey(key);
     onActiveChange(key);
   }
 
@@ -368,6 +371,7 @@ const NodeList: React.FC<NodeListProps> = ({
           onFocus={onFocus}
           onBlur={onBlur}
           value=""
+          onChange={noop}
         />
       </div>
 
@@ -397,7 +401,7 @@ const NodeList: React.FC<NodeListProps> = ({
             <MotionTreeNode
               {...restProps}
               {...treeNodeProps}
-              active={focused && key === activeKey}
+              active={activeKey !== null && key === activeKey}
               pos={pos}
               data={treeNode.data}
               isStart={isStart}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index de186577..62e651c0 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -54,6 +54,7 @@ export interface TreeProps {
   style?: React.CSSProperties;
   focusable?: boolean;
   tabIndex?: number;
+  activeKey?: Key;
   children?: React.ReactNode;
   treeData?: DataNode[]; // Generate treeNode by children
   showLine?: boolean;
@@ -267,6 +268,11 @@ class Tree extends React.Component<TreeProps, TreeState> {
       return (!prevProps && name in props) || (prevProps && prevProps[name] !== props[name]);
     }
 
+    // ================== ActiveKey ==================
+    if ('activeKey' in props) {
+      newState.activeKey = props.activeKey;
+    }
+
     // ================== Tree Node ==================
     let treeData: DataNode[];
 
@@ -920,6 +926,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
           itemHeight={itemHeight}
           focusable={focusable}
           focused={focused}
+          activeKey={activeKey}
           tabIndex={tabIndex}
           onFocus={this.onFocus}
           onBlur={this.onBlur}
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index 47bd6133..0cfc7c1a 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -189,4 +189,34 @@ describe('Tree Accessibility', () => {
     const wrapper = mount(<Tree disabled />);
     expect(wrapper.find('input').props().disabled).toBeTruthy();
   });
+
+  it('`activeKey` can be controlled', () => {
+    const wrapper = mount(
+      <Tree
+        defaultExpandAll
+        activeKey="bamboo"
+        treeData={[{ key: 'light', children: [{ key: 'bamboo' }, { key: 'cat' }] }]}
+      />,
+    );
+
+    expect(
+      wrapper
+        .find(InternalTreeNode)
+        .find('.rc-tree-treenode')
+        .at(1)
+        .hasClass('rc-tree-treenode-active'),
+    ).toBeTruthy();
+
+    // Key operation
+    wrapper.find('input').simulate('focus');
+    wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
+
+    expect(
+      wrapper
+        .find(InternalTreeNode)
+        .find('.rc-tree-treenode')
+        .at(1)
+        .hasClass('rc-tree-treenode-active'),
+    ).toBeTruthy();
+  });
 });

From ffea5e63873a15795d77b180af50bbda0e4f3ad8 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 11:13:22 +0800
Subject: [PATCH 023/397] v3.0.0-alpha.25

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2200a519..d59028ab 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.24",
+  "version": "3.0.0-alpha.25",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From db6831b45005c59b5a61741c605a307dd204b095 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 12:06:30 +0800
Subject: [PATCH 024/397] fix: key is 0 still need in count

---
 src/utils/treeUtil.ts | 2 +-
 tests/util.spec.js    | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index a44f5248..274f3cca 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -150,7 +150,7 @@ export function traverseDataNodes(
         node,
         index,
         pos,
-        key: node.key || pos,
+        key: node.key !== null ? node.key : pos,
         parentPos: parent.node ? parent.pos : null,
         level: parent.level + 1,
       };
diff --git a/tests/util.spec.js b/tests/util.spec.js
index 38fad516..e250b479 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -112,6 +112,11 @@ describe('Util', () => {
     expect(treeNodes[0].props.children[0].props.value).toBe('0-0-0');
   });
 
+  it('convertDataToEntities', () => {
+    const entities = convertDataToEntities([{ key: 'parent', children: [{ key: 0 }, { key: 1 }] }]);
+    expect(Object.keys(entities.keyEntities).sort()).toEqual(['0', '1', 'parent']);
+  });
+
   it('convertTreeToEntities with additional handler', () => {
     const onProcessFinished = jest.fn();
 

From e1e4f154eab916ec7408952211346d9bd813d496 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 12:07:21 +0800
Subject: [PATCH 025/397] v3.0.0-alpha.26

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d59028ab..6fee4093 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.25",
+  "version": "3.0.0-alpha.26",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 609b760444fefdd8ba214e7c040a5aed9f1f95c5 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 12:13:28 +0800
Subject: [PATCH 026/397] chore: eventKey can be number type

---
 src/TreeNode.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index f32f68a2..32cc3c3e 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -61,13 +61,13 @@ export interface TreeNodeState {
 
 class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeState> {
   static propTypes = {
-    eventKey: PropTypes.string, // Pass by parent `cloneElement`
     prefixCls: PropTypes.string,
     className: PropTypes.string,
     style: PropTypes.object,
     onSelect: PropTypes.func,
 
     // By parent
+    eventKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
     expanded: PropTypes.bool,
     selected: PropTypes.bool,
     checked: PropTypes.bool,

From e0198bd458be9d53c763a70c251f9a464ff2477c Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 12:14:24 +0800
Subject: [PATCH 027/397] v3.0.0-alpha.27

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6fee4093..43af49d8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.26",
+  "version": "3.0.0-alpha.27",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 79848c24f3a3d7ce8b51c36ea56140480b015e72 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 14:41:27 +0800
Subject: [PATCH 028/397] chore: clean up code

---
 src/NodeList.tsx | 123 ++------------------------------
 src/Tree.tsx     | 180 ++++++++++++++++++++++++++++++++++++++---------
 2 files changed, 154 insertions(+), 149 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 92790f98..0b33c5cc 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -4,12 +4,10 @@
 
 import * as React from 'react';
 import VirtualList from 'rc-virtual-list';
-import KeyCode from 'rc-util/lib/KeyCode';
 import { FlattenNode, Key, DataEntity, DataNode } from './interface';
 import MotionTreeNode from './MotionTreeNode';
-import { TreeContext } from './contextTypes';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
-import { getTreeNodeProps, getKey, convertNodePropsToEventData } from './utils/treeUtil';
+import { getTreeNodeProps, getKey } from './utils/treeUtil';
 
 const HIDDEN_STYLE = {
   width: 0,
@@ -55,7 +53,7 @@ interface NodeListProps {
   data: FlattenNode[];
   motion: any;
   focusable?: boolean;
-  activeKey?: Key;
+  activeItem: FlattenNode;
   focused?: boolean;
   tabIndex: number;
   checkable?: boolean;
@@ -143,7 +141,7 @@ const NodeList: React.FC<NodeListProps> = props => {
     itemHeight,
 
     focusable,
-    activeKey,
+    activeItem,
     focused,
     tabIndex,
 
@@ -240,120 +238,13 @@ const NodeList: React.FC<NodeListProps> = props => {
   };
 
   // =========================== Accessibility ==========================
-  function setActiveKey(key: Key) {
-    onActiveChange(key);
-  }
-
-  const activeItem = data.find(({ data: { key } }) => key === activeKey);
-
-  const changeActive = (offset: number) => {
-    let index = data.findIndex(({ data: { key } }) => key === activeKey);
-
-    // Align with index
-    if (index === -1 && offset < 0) {
-      index = data.length;
-    }
-
-    index = (index + offset + data.length) % data.length;
-
-    const item = data[index];
-    if (item) {
-      setActiveKey(item.data.key);
-    } else {
-      setActiveKey(null);
-    }
-  };
-
   // Clean up `activeKey` if blur
   React.useEffect(() => {
     if (!focused) {
-      setActiveKey(null);
+      onActiveChange(null);
     }
   }, [focused]);
 
-  // ============================= Keyboard =============================
-  const { onNodeExpand, onNodeCheck, onNodeSelect } = React.useContext(TreeContext);
-
-  const onInternalKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
-    // >>>>>>>>>> Direction
-    switch (event.which) {
-      case KeyCode.UP: {
-        changeActive(-1);
-        event.preventDefault();
-        break;
-      }
-      case KeyCode.DOWN: {
-        changeActive(1);
-        event.preventDefault();
-        break;
-      }
-    }
-
-    if (activeItem && activeItem.data) {
-      // >>>>>>>>>> Expand & Selection
-      const expandable =
-        activeItem.data.isLeaf === false || !!(activeItem.data.children || []).length;
-      const eventNode = convertNodePropsToEventData({
-        ...getTreeNodeProps(activeKey, treeNodeRequiredProps),
-        data: activeItem.data,
-        active: true,
-      });
-
-      switch (event.which) {
-        // >>> Expand
-        case KeyCode.LEFT: {
-          // Collapse if possible
-          if (expandable && expandedKeys.includes(activeKey)) {
-            onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
-          } else if (activeItem.parent) {
-            setActiveKey(activeItem.parent.data.key);
-          }
-          event.preventDefault();
-          break;
-        }
-        case KeyCode.RIGHT: {
-          // Expand if possible
-          if (expandable && !expandedKeys.includes(activeKey)) {
-            onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
-          } else if (activeItem.children && activeItem.children.length) {
-            setActiveKey(activeItem.children[0].data.key);
-          }
-          event.preventDefault();
-          break;
-        }
-
-        // Selection
-        case KeyCode.ENTER:
-        case KeyCode.SPACE: {
-          if (
-            checkable &&
-            !eventNode.disabled &&
-            eventNode.checkable !== false &&
-            !eventNode.disableCheckbox
-          ) {
-            onNodeCheck(
-              {} as React.MouseEvent<HTMLDivElement>,
-              eventNode,
-              !checkedKeys.includes(activeKey),
-            );
-          } else if (
-            !checkable &&
-            selectable &&
-            !eventNode.disabled &&
-            eventNode.selectable !== false
-          ) {
-            onNodeSelect({} as React.MouseEvent<HTMLDivElement>, eventNode);
-          }
-          break;
-        }
-      }
-    }
-
-    if (onKeyDown) {
-      onKeyDown(event);
-    }
-  };
-
   return (
     <>
       {focused && activeItem && (
@@ -367,7 +258,7 @@ const NodeList: React.FC<NodeListProps> = props => {
           style={HIDDEN_STYLE}
           disabled={focusable === false || disabled}
           tabIndex={focusable !== false ? tabIndex : null}
-          onKeyDown={onInternalKeyDown}
+          onKeyDown={onKeyDown}
           onFocus={onFocus}
           onBlur={onBlur}
           value=""
@@ -401,7 +292,7 @@ const NodeList: React.FC<NodeListProps> = props => {
             <MotionTreeNode
               {...restProps}
               {...treeNodeProps}
-              active={activeKey !== null && key === activeKey}
+              active={activeItem && key === activeItem.data.key}
               pos={pos}
               data={treeNode.data}
               isStart={isStart}
@@ -412,7 +303,7 @@ const NodeList: React.FC<NodeListProps> = props => {
               onMotionEnd={onMotionEnd}
               treeNodeRequiredProps={treeNodeRequiredProps}
               onMouseMove={() => {
-                setActiveKey(null);
+                onActiveChange(null);
               }}
             />
           );
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 62e651c0..f109d597 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -3,6 +3,7 @@
 
 import * as React from 'react';
 import PropTypes from 'prop-types';
+import KeyCode from 'rc-util/lib/KeyCode';
 import warning from 'rc-util/lib/warning';
 import classNames from 'classnames';
 import { polyfill } from 'react-lifecycles-compat';
@@ -35,6 +36,7 @@ import {
   convertDataToEntities,
   warningWithoutKey,
   convertNodePropsToEventData,
+  getTreeNodeProps,
 } from './utils/treeUtil';
 import NodeList, { MOTION_KEY, MotionEntity } from './NodeList';
 
@@ -54,7 +56,6 @@ export interface TreeProps {
   style?: React.CSSProperties;
   focusable?: boolean;
   tabIndex?: number;
-  activeKey?: Key;
   children?: React.ReactNode;
   treeData?: DataNode[]; // Generate treeNode by children
   showLine?: boolean;
@@ -268,11 +269,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
       return (!prevProps && name in props) || (prevProps && prevProps[name] !== props[name]);
     }
 
-    // ================== ActiveKey ==================
-    if ('activeKey' in props) {
-      newState.activeKey = props.activeKey;
-    }
-
     // ================== Tree Node ==================
     let treeData: DataNode[];
 
@@ -802,10 +798,151 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
+  getTreeNodeRequiredProps = () => {
+    const {
+      expandedKeys,
+      selectedKeys,
+      loadedKeys,
+      loadingKeys,
+      checkedKeys,
+      halfCheckedKeys,
+      dragOverNodeKey,
+      dropPosition,
+      keyEntities,
+    } = this.state;
+    return {
+      expandedKeys,
+      selectedKeys,
+      loadedKeys,
+      loadingKeys,
+      checkedKeys,
+      halfCheckedKeys,
+      dragOverNodeKey,
+      dropPosition,
+      keyEntities,
+    };
+  };
+
+  // =========================== Keyboard ===========================
   onActiveChange = (activeKey: Key) => {
     this.setState({ activeKey });
   };
 
+  getActiveItem = () => {
+    const { activeKey, flattenNodes } = this.state;
+    if (activeKey === null) {
+      return null;
+    }
+
+    return flattenNodes.find(({ data: { key } }) => key === activeKey) || null;
+  };
+
+  offsetActiveKey = (offset: number) => {
+    const { flattenNodes, activeKey } = this.state;
+
+    let index = flattenNodes.findIndex(({ data: { key } }) => key === activeKey);
+
+    // Align with index
+    if (index === -1 && offset < 0) {
+      index = flattenNodes.length;
+    }
+
+    index = (index + offset + flattenNodes.length) % flattenNodes.length;
+
+    const item = flattenNodes[index];
+    if (item) {
+      this.onActiveChange(item.data.key);
+    } else {
+      this.onActiveChange(null);
+    }
+  };
+
+  onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
+    const { activeKey, expandedKeys, checkedKeys } = this.state;
+    const { onKeyDown, checkable, selectable } = this.props;
+
+    // >>>>>>>>>> Direction
+    switch (event.which) {
+      case KeyCode.UP: {
+        this.offsetActiveKey(-1);
+        event.preventDefault();
+        break;
+      }
+      case KeyCode.DOWN: {
+        this.offsetActiveKey(1);
+        event.preventDefault();
+        break;
+      }
+    }
+
+    // >>>>>>>>>> Expand & Selection
+    const activeItem = this.getActiveItem();
+    if (activeItem && activeItem.data) {
+      const treeNodeRequiredProps = this.getTreeNodeRequiredProps();
+
+      const expandable =
+        activeItem.data.isLeaf === false || !!(activeItem.data.children || []).length;
+      const eventNode = convertNodePropsToEventData({
+        ...getTreeNodeProps(activeKey, treeNodeRequiredProps),
+        data: activeItem.data,
+        active: true,
+      });
+
+      switch (event.which) {
+        // >>> Expand
+        case KeyCode.LEFT: {
+          // Collapse if possible
+          if (expandable && expandedKeys.includes(activeKey)) {
+            this.onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
+          } else if (activeItem.parent) {
+            this.onActiveChange(activeItem.parent.data.key);
+          }
+          event.preventDefault();
+          break;
+        }
+        case KeyCode.RIGHT: {
+          // Expand if possible
+          if (expandable && !expandedKeys.includes(activeKey)) {
+            this.onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
+          } else if (activeItem.children && activeItem.children.length) {
+            this.onActiveChange(activeItem.children[0].data.key);
+          }
+          event.preventDefault();
+          break;
+        }
+
+        // Selection
+        case KeyCode.ENTER:
+        case KeyCode.SPACE: {
+          if (
+            checkable &&
+            !eventNode.disabled &&
+            eventNode.checkable !== false &&
+            !eventNode.disableCheckbox
+          ) {
+            this.onNodeCheck(
+              {} as React.MouseEvent<HTMLDivElement>,
+              eventNode,
+              !checkedKeys.includes(activeKey),
+            );
+          } else if (
+            !checkable &&
+            selectable &&
+            !eventNode.disabled &&
+            eventNode.selectable !== false
+          ) {
+            this.onNodeSelect({} as React.MouseEvent<HTMLDivElement>, eventNode);
+          }
+          break;
+        }
+      }
+    }
+
+    if (onKeyDown) {
+      onKeyDown(event);
+    }
+  };
+
   /**
    * Only update the value which is not in props
    */
@@ -826,21 +963,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   render() {
-    const {
-      focused,
-      flattenNodes,
-      keyEntities,
-      expandedKeys,
-      selectedKeys,
-      checkedKeys,
-      loadedKeys,
-      loadingKeys,
-      halfCheckedKeys,
-      dragging,
-      dragOverNodeKey,
-      dropPosition,
-      activeKey,
-    } = this.state;
+    const { focused, flattenNodes, keyEntities, dragging, activeKey } = this.state;
     const {
       prefixCls,
       className,
@@ -861,7 +984,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
       filterTreeNode,
       height,
       itemHeight,
-      onKeyDown,
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
 
@@ -911,27 +1033,19 @@ class Tree extends React.Component<TreeProps, TreeState> {
           disabled={disabled}
           selectable={selectable}
           checkable={!!checkable}
-          keyEntities={keyEntities}
-          expandedKeys={expandedKeys}
-          selectedKeys={selectedKeys}
-          checkedKeys={checkedKeys}
-          loadedKeys={loadedKeys}
-          loadingKeys={loadingKeys}
-          halfCheckedKeys={halfCheckedKeys}
-          dragOverNodeKey={dragOverNodeKey}
-          dropPosition={dropPosition}
           motion={motion}
           dragging={dragging}
           height={height}
           itemHeight={itemHeight}
           focusable={focusable}
           focused={focused}
-          activeKey={activeKey}
           tabIndex={tabIndex}
+          activeItem={this.getActiveItem()}
           onFocus={this.onFocus}
           onBlur={this.onBlur}
-          onKeyDown={onKeyDown}
+          onKeyDown={this.onKeyDown}
           onActiveChange={this.onActiveChange}
+          {...this.getTreeNodeRequiredProps()}
           {...domProps}
         />
       </TreeContext.Provider>

From e625037bad9b08fe8d4840a0cd7384d9a0208c3b Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 15:41:06 +0800
Subject: [PATCH 029/397] feat: Support scrollTo with key

---
 examples/basic.jsx | 31 +++++++++++++++++++++++++++++--
 package.json       |  2 +-
 src/NodeList.tsx   | 20 +++++++++++++++++---
 src/Tree.tsx       | 10 +++++++++-
 src/interface.tsx  |  2 ++
 5 files changed, 58 insertions(+), 7 deletions(-)

diff --git a/examples/basic.jsx b/examples/basic.jsx
index 2d1d49de..8449ba48 100644
--- a/examples/basic.jsx
+++ b/examples/basic.jsx
@@ -19,6 +19,14 @@ const treeData = [
         children: [
           { key: '0-0-1-0', title: 'parent 1-2-0', disableCheckbox: true },
           { key: '0-0-1-1', title: 'parent 1-2-1' },
+          { key: '0-0-1-2', title: 'parent 1-2-2' },
+          { key: '0-0-1-3', title: 'parent 1-2-3' },
+          { key: '0-0-1-4', title: 'parent 1-2-4' },
+          { key: '0-0-1-5', title: 'parent 1-2-5' },
+          { key: '0-0-1-6', title: 'parent 1-2-6' },
+          { key: '0-0-1-7', title: 'parent 1-2-7' },
+          { key: '0-0-1-8', title: 'parent 1-2-8' },
+          { key: '0-0-1-9', title: 'parent 1-2-9' },
         ],
       },
     ],
@@ -42,6 +50,8 @@ class Demo extends React.Component {
       defaultSelectedKeys: keys,
       defaultCheckedKeys: keys,
     };
+
+    this.treeRef = React.createRef();
   }
 
   onExpand = expandedKeys => {
@@ -141,9 +151,26 @@ class Demo extends React.Component {
         />
 
         <h2>Select</h2>
-        <Tree className="myCls" defaultExpandAll treeData={treeData} onSelect={this.onSelect} />
+        <Tree
+          ref={this.treeRef}
+          className="myCls"
+          defaultExpandAll
+          treeData={treeData}
+          onSelect={this.onSelect}
+          height={150}
+        />
 
-        <input aria-label="bad" />
+        <button
+          type="button"
+          onClick={() => {
+            setTimeout(() => {
+              console.log('scroll!!!');
+              this.treeRef.current.scrollTo({ key: '0-0-1-9' });
+            }, 100);
+          }}
+        >
+          Scroll Last
+        </button>
       </div>
     );
   }
diff --git a/package.json b/package.json
index 43af49d8..69b8ac8a 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
     "prop-types": "^15.5.8",
     "rc-animate": "^2.9.2",
     "rc-util": "^4.11.0",
-    "rc-virtual-list": "^0.0.0-alpha.18",
+    "rc-virtual-list": "^0.0.0-alpha.27",
     "react-lifecycles-compat": "^3.0.4"
   }
 }
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 0b33c5cc..d659b9b6 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -4,7 +4,7 @@
 
 import * as React from 'react';
 import VirtualList from 'rc-virtual-list';
-import { FlattenNode, Key, DataEntity, DataNode } from './interface';
+import { FlattenNode, Key, DataEntity, DataNode, ScrollTo } from './interface';
 import MotionTreeNode from './MotionTreeNode';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
 import { getTreeNodeProps, getKey } from './utils/treeUtil';
@@ -46,6 +46,10 @@ const MotionFlattenData: FlattenNode = {
   isEnd: [],
 };
 
+export interface NodeListRef {
+  scrollTo: ScrollTo;
+}
+
 interface NodeListProps {
   prefixCls: string;
   className: string;
@@ -117,7 +121,7 @@ function getAccessibilityPath(item: FlattenNode): string {
   return path;
 }
 
-const NodeList: React.FC<NodeListProps> = props => {
+const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (props, ref) => {
   const {
     prefixCls,
     data,
@@ -153,6 +157,12 @@ const NodeList: React.FC<NodeListProps> = props => {
     ...domProps
   } = props;
 
+  // =============================== Ref ================================
+  const listRef = React.useRef<VirtualList<FlattenNode>>(null);
+  React.useImperativeHandle(ref, () => ({
+    scrollTo: listRef.current.scrollTo,
+  }));
+
   // ============================== Motion ==============================
   const [disableVirtual, setDisableVirtual] = React.useState(false);
   const [prevExpandedKeys, setPrevExpandedKeys] = React.useState(expandedKeys);
@@ -266,7 +276,7 @@ const NodeList: React.FC<NodeListProps> = props => {
         />
       </div>
 
-      <VirtualList
+      <VirtualList<FlattenNode>
         {...domProps}
         disabled={disableVirtual}
         data={mergedData}
@@ -275,6 +285,7 @@ const NodeList: React.FC<NodeListProps> = props => {
         itemHeight={itemHeight}
         onSkipRender={onMotionEnd}
         prefixCls={`${prefixCls}-list`}
+        ref={listRef}
       >
         {(treeNode: FlattenNode) => {
           const {
@@ -313,4 +324,7 @@ const NodeList: React.FC<NodeListProps> = props => {
   );
 };
 
+const NodeList = React.forwardRef(RefNodeList);
+NodeList.displayName = 'NodeList';
+
 export default NodeList;
diff --git a/src/Tree.tsx b/src/Tree.tsx
index f109d597..47cc12c6 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -29,6 +29,7 @@ import {
   DataEntity,
   EventDataNode,
   NodeInstance,
+  ScrollTo,
 } from './interface';
 import {
   flattenTreeData,
@@ -38,7 +39,7 @@ import {
   convertNodePropsToEventData,
   getTreeNodeProps,
 } from './utils/treeUtil';
-import NodeList, { MOTION_KEY, MotionEntity } from './NodeList';
+import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
 
 interface CheckInfo {
   event: 'check';
@@ -259,6 +260,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   dragNode: NodeInstance;
 
+  listRef = React.createRef<NodeListRef>();
+
   static getDerivedStateFromProps(props: TreeProps, prevState: TreeState) {
     const { prevProps } = prevState;
     const newState: Partial<TreeState> = {
@@ -962,6 +965,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
+  scrollTo: ScrollTo = scroll => {
+    this.listRef.current.scrollTo(scroll);
+  };
+
   render() {
     const { focused, flattenNodes, keyEntities, dragging, activeKey } = this.state;
     const {
@@ -1022,6 +1029,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         }}
       >
         <NodeList
+          ref={this.listRef}
           className={classNames(prefixCls, className, {
             [`${prefixCls}-show-line`]: showLine,
             [`${prefixCls}-focused`]: focused,
diff --git a/src/interface.tsx b/src/interface.tsx
index b040c884..db85db6a 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -71,3 +71,5 @@ export interface FlattenNode {
   isStart: boolean[];
   isEnd: boolean[];
 }
+
+export type ScrollTo = (scroll: { key: Key }) => void;

From 164d3fe58628c2757f8641eb2cbe135652dbfb4a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 15:41:45 +0800
Subject: [PATCH 030/397] v3.0.0-alpha.28

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 69b8ac8a..7cbe09f7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.27",
+  "version": "3.0.0-alpha.28",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 01bf1703484906ae700578076e66dc222e4fb6ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=81=8F=E5=8F=B3?= <afc163@gmail.com>
Date: Wed, 18 Sep 2019 16:02:52 +0800
Subject: [PATCH 031/397] :hammer: remove cross-env
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

这个变量貌似没什么意义。
---
 package.json | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 7cbe09f7..2917126d 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
   "main": "./lib/index",
   "module": "./es/index",
   "scripts": {
-    "start": "cross-env NODE_ENV=development father doc dev --storybook",
+    "start": "father doc dev --storybook",
     "build": "father doc build --storybook",
     "compile": "father build",
     "prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish",
@@ -46,7 +46,6 @@
     "@types/react": "^16.8.19",
     "@types/react-dom": "^16.8.4",
     "@types/warning": "^3.0.0",
-    "cross-env": "^6.0.0",
     "css-animation": "^1.2.0",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",

From b050bba8d74a440ac391d418cbdd14e724251014 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 16:33:42 +0800
Subject: [PATCH 032/397] chore: Remove useless ts define

---
 src/NodeList.tsx            | 12 +++---------
 src/Tree.tsx                |  7 +++++++
 src/index.ts                | 10 +---------
 tests/Accessibility.spec.js | 30 ------------------------------
 tests/Tree.spec.js          | 12 +++++++++++-
 5 files changed, 22 insertions(+), 49 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index d659b9b6..08a7411d 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -160,7 +160,9 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
   // =============================== Ref ================================
   const listRef = React.useRef<VirtualList<FlattenNode>>(null);
   React.useImperativeHandle(ref, () => ({
-    scrollTo: listRef.current.scrollTo,
+    scrollTo: scroll => {
+      listRef.current.scrollTo(scroll);
+    },
   }));
 
   // ============================== Motion ==============================
@@ -247,14 +249,6 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
     keyEntities,
   };
 
-  // =========================== Accessibility ==========================
-  // Clean up `activeKey` if blur
-  React.useEffect(() => {
-    if (!focused) {
-      onActiveChange(null);
-    }
-  }, [focused]);
-
   return (
     <>
       {focused && activeItem && (
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 47cc12c6..4ed24f5f 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -40,6 +40,7 @@ import {
   getTreeNodeProps,
 } from './utils/treeUtil';
 import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
+import TreeNode from './TreeNode';
 
 interface CheckInfo {
   event: 'check';
@@ -232,6 +233,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
     defaultSelectedKeys: [],
   };
 
+  static TreeNode = TreeNode;
+
   delayedDragEnterLogic: Record<Key, number>;
 
   state: TreeState = {
@@ -363,6 +366,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         newState.halfCheckedKeys = halfCheckedKeys;
       }
     }
+
     // ================= loadedKeys ==================
     if (needSync('loadedKeys')) {
       newState.loadedKeys = props.loadedKeys;
@@ -788,6 +792,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   onFocus: React.FocusEventHandler<HTMLDivElement> = (...args) => {
     const { onFocus } = this.props;
     this.setState({ focused: true });
+
     if (onFocus) {
       onFocus(...args);
     }
@@ -796,6 +801,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
   onBlur: React.FocusEventHandler<HTMLDivElement> = (...args) => {
     const { onBlur } = this.props;
     this.setState({ focused: false });
+    this.onActiveChange(null);
+
     if (onBlur) {
       onBlur(...args);
     }
diff --git a/src/index.ts b/src/index.ts
index f3f771f6..d5cb7cd9 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,13 +1,5 @@
-import OriginTree, { TreeProps } from './Tree';
+import Tree, { TreeProps } from './Tree';
 import TreeNode, { TreeNodeProps } from './TreeNode';
 
-type OriginTreeType = typeof OriginTree;
-interface TreeType extends OriginTreeType {
-  TreeNode: typeof TreeNode;
-}
-
-const Tree = OriginTree as TreeType;
-Tree.TreeNode = TreeNode;
-
 export { TreeNode, TreeProps, TreeNodeProps };
 export default Tree;
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index 0cfc7c1a..47bd6133 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -189,34 +189,4 @@ describe('Tree Accessibility', () => {
     const wrapper = mount(<Tree disabled />);
     expect(wrapper.find('input').props().disabled).toBeTruthy();
   });
-
-  it('`activeKey` can be controlled', () => {
-    const wrapper = mount(
-      <Tree
-        defaultExpandAll
-        activeKey="bamboo"
-        treeData={[{ key: 'light', children: [{ key: 'bamboo' }, { key: 'cat' }] }]}
-      />,
-    );
-
-    expect(
-      wrapper
-        .find(InternalTreeNode)
-        .find('.rc-tree-treenode')
-        .at(1)
-        .hasClass('rc-tree-treenode-active'),
-    ).toBeTruthy();
-
-    // Key operation
-    wrapper.find('input').simulate('focus');
-    wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
-
-    expect(
-      wrapper
-        .find(InternalTreeNode)
-        .find('.rc-tree-treenode')
-        .at(1)
-        .hasClass('rc-tree-treenode-active'),
-    ).toBeTruthy();
-  });
 });
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 632c3ce0..b4060197 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -3,11 +3,11 @@ react/no-unused-state, react/prop-types, no-return-assign, import/no-named-as-de
 import React from 'react';
 import { render, mount } from 'enzyme';
 import { renderToJson } from 'enzyme-to-json';
+import { resetWarned } from 'rc-util/lib/warning';
 import Tree, { TreeNode } from '../src';
 import { InternalTreeNode } from '../src/TreeNode';
 import { objectMatcher, spyConsole, spyError } from './util';
 import { convertNodePropsToEventData } from '../src/utils/treeUtil';
-import { resetWarned } from 'rc-util/lib/warning';
 
 const OPEN_CLASSNAME = '.rc-tree-switcher_open';
 const CHECKED_CLASSNAME = '.rc-tree-checkbox-checked';
@@ -1313,4 +1313,14 @@ describe('Tree Basic', () => {
       .simulate('click');
     expect(onCheck).toHaveBeenCalledWith([]);
   });
+
+  it('scrollTo should work', () => {
+    const wrapper = mount(<Tree />);
+
+    const scrollTo = jest.fn();
+    wrapper.find('NodeList List').instance().scrollTo = scrollTo;
+    wrapper.instance().scrollTo({ key: 'light' });
+
+    expect(scrollTo).toHaveBeenCalled();
+  });
 });

From 9196f08cc5f6c125bb626cfb99599c648b0bf4af Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 16:37:10 +0800
Subject: [PATCH 033/397] v3.0.0-alpha.29

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2917126d..f2ebd408 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.28",
+  "version": "3.0.0-alpha.29",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 2fe64e18089e6a28d05f82f9daa1edafa1b8796a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 17:06:06 +0800
Subject: [PATCH 034/397] feat: Keyboard operation will also scroll to target
 position

---
 examples/basic.jsx | 1 -
 src/Tree.tsx       | 4 +++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/examples/basic.jsx b/examples/basic.jsx
index 8449ba48..1e2afa84 100644
--- a/examples/basic.jsx
+++ b/examples/basic.jsx
@@ -2,7 +2,6 @@
 no-alert, no-console, react/no-find-dom-node */
 import React from 'react';
 import PropTypes from 'prop-types';
-import ReactDOM from 'react-dom';
 import '../assets/index.less';
 import './basic.less';
 import Tree, { TreeNode } from '../src';
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 4ed24f5f..b9edfa7d 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -861,7 +861,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
     const item = flattenNodes[index];
     if (item) {
-      this.onActiveChange(item.data.key);
+      const { key } = item.data;
+      this.onActiveChange(key);
+      this.scrollTo({ key });
     } else {
       this.onActiveChange(null);
     }

From 0675c795e9b7e1bbea0a640ee9cdfb09d912837a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 17:06:45 +0800
Subject: [PATCH 035/397] v3.0.0-alpha.30

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f2ebd408..f37f6fe9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.29",
+  "version": "3.0.0-alpha.30",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From edc42cd92afb962ec427b9c641e92d5464dfeee9 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 17:18:33 +0800
Subject: [PATCH 036/397] fix: Remove key type convert in virtual list, it's
 useless now

---
 examples/basic.jsx | 1 +
 src/NodeList.tsx   | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/examples/basic.jsx b/examples/basic.jsx
index 1e2afa84..b24598fd 100644
--- a/examples/basic.jsx
+++ b/examples/basic.jsx
@@ -26,6 +26,7 @@ const treeData = [
           { key: '0-0-1-7', title: 'parent 1-2-7' },
           { key: '0-0-1-8', title: 'parent 1-2-8' },
           { key: '0-0-1-9', title: 'parent 1-2-9' },
+          { key: 1128, title: 1128 },
         ],
       },
     ],
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 08a7411d..64de9348 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -106,7 +106,7 @@ function itemKey(item: FlattenNode) {
     data: { key },
     pos,
   } = item;
-  return String(getKey(key, pos));
+  return getKey(key, pos);
 }
 
 function getAccessibilityPath(item: FlattenNode): string {

From fffcbd318dff1e7a275630320fbb54356a4e18b8 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Sep 2019 17:19:30 +0800
Subject: [PATCH 037/397] v3.0.0-alpha.31

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f37f6fe9..148a68a6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.30",
+  "version": "3.0.0-alpha.31",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 6215ffa247594e0f2b3fc6177f35f386b2a18571 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 19 Sep 2019 10:58:12 +0800
Subject: [PATCH 038/397] feat: add onActiveChange event

---
 package.json                |  2 +-
 src/Tree.tsx                | 15 ++++++++++++++-
 tests/Accessibility.spec.js | 12 ++++++++++++
 3 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 148a68a6..241f7b8c 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
     "prop-types": "^15.5.8",
     "rc-animate": "^2.9.2",
     "rc-util": "^4.11.0",
-    "rc-virtual-list": "^0.0.0-alpha.27",
+    "rc-virtual-list": "^0.0.0-alpha.28",
     "react-lifecycles-compat": "^3.0.4"
   }
 }
diff --git a/src/Tree.tsx b/src/Tree.tsx
index b9edfa7d..87eba74e 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -131,6 +131,11 @@ export interface TreeProps {
     dropPosition: number;
     dropToGap: boolean;
   }) => void;
+  /**
+   * Used for `rc-tree-select` only.
+   * Do not use in your production code directly since this will be refactor.
+   */
+  onActiveChange?: (key: Key) => void;
   filterTreeNode?: (treeNode: EventDataNode) => boolean;
   motion?: any;
   switcherIcon?: IconType;
@@ -835,7 +840,16 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   // =========================== Keyboard ===========================
   onActiveChange = (activeKey: Key) => {
+    const { onActiveChange } = this.props;
+
     this.setState({ activeKey });
+    if (activeKey !== null) {
+      this.scrollTo({ key: activeKey });
+    }
+
+    if (onActiveChange) {
+      onActiveChange(activeKey);
+    }
   };
 
   getActiveItem = () => {
@@ -863,7 +877,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
     if (item) {
       const { key } = item.data;
       this.onActiveChange(key);
-      this.scrollTo({ key });
     } else {
       this.onActiveChange(null);
     }
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index 47bd6133..db78520e 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -15,12 +15,18 @@ describe('Tree Accessibility', () => {
       const onFocus = jest.fn();
       const onBlur = jest.fn();
       const onKeyDown = jest.fn();
+      const onActiveChange = jest.fn();
 
       function checkKeyDownTrigger() {
         expect(onKeyDown).toHaveBeenCalled();
         onKeyDown.mockReset();
       }
 
+      function checkActiveTrigger(key) {
+        expect(onActiveChange).toHaveBeenCalledWith(key);
+        onActiveChange.mockReset();
+      }
+
       const wrapper = mount(
         <Tree
           {...props}
@@ -28,6 +34,7 @@ describe('Tree Accessibility', () => {
           onFocus={onFocus}
           onBlur={onBlur}
           onKeyDown={onKeyDown}
+          onActiveChange={onActiveChange}
           defaultExpandAll
           treeData={[{ key: 'parent', children: [{ key: 'child 1' }, { key: 'child 2' }] }]}
         />,
@@ -47,6 +54,7 @@ describe('Tree Accessibility', () => {
           .hasClass('rc-tree-treenode-active'),
       ).toBeTruthy();
       checkKeyDownTrigger();
+      checkActiveTrigger('child 2');
 
       // Arrow down: first one
       wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
@@ -58,6 +66,7 @@ describe('Tree Accessibility', () => {
           .hasClass('rc-tree-treenode-active'),
       ).toBeTruthy();
       checkKeyDownTrigger();
+      checkActiveTrigger('parent');
 
       // Arrow up: last one again
       wrapper.find('input').simulate('keyDown', { which: KeyCode.UP });
@@ -69,6 +78,7 @@ describe('Tree Accessibility', () => {
           .hasClass('rc-tree-treenode-active'),
       ).toBeTruthy();
       checkKeyDownTrigger();
+      checkActiveTrigger('child 2');
 
       // Arrow left: parent
       wrapper.find('input').simulate('keyDown', { which: KeyCode.LEFT });
@@ -80,6 +90,7 @@ describe('Tree Accessibility', () => {
           .hasClass('rc-tree-treenode-active'),
       ).toBeTruthy();
       checkKeyDownTrigger();
+      checkActiveTrigger('parent');
 
       // Arrow left: collapse
       wrapper.find('input').simulate('keyDown', { which: KeyCode.LEFT });
@@ -117,6 +128,7 @@ describe('Tree Accessibility', () => {
           .hasClass('rc-tree-treenode-active'),
       ).toBeTruthy();
       checkKeyDownTrigger();
+      checkActiveTrigger('child 1');
 
       // SPACE: confirm
       wrapper.find('input').simulate('keyDown', { which: KeyCode.SPACE });

From 7646fbf8127ed6778189ac7ee771394a02d20d4c Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 19 Sep 2019 10:58:58 +0800
Subject: [PATCH 039/397] v3.0.0-alpha.32

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 241f7b8c..b04b4c1c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.31",
+  "version": "3.0.0-alpha.32",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 4eabc3de51025a657a0c88e7f835d13c5ec3c92c Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 19 Sep 2019 14:27:16 +0800
Subject: [PATCH 040/397] fix: Update propTypes define

---
 src/Tree.tsx | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 87eba74e..ba59e133 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -169,6 +169,8 @@ interface TreeState {
   prevProps: TreeProps;
 }
 
+const keyPropType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
+
 class Tree extends React.Component<TreeProps, TreeState> {
   static propTypes = {
     prefixCls: PropTypes.string,
@@ -189,15 +191,12 @@ class Tree extends React.Component<TreeProps, TreeState> {
     defaultExpandParent: PropTypes.bool,
     autoExpandParent: PropTypes.bool,
     defaultExpandAll: PropTypes.bool,
-    defaultExpandedKeys: PropTypes.arrayOf(PropTypes.string),
-    expandedKeys: PropTypes.arrayOf(PropTypes.string),
-    defaultCheckedKeys: PropTypes.arrayOf(PropTypes.string),
-    checkedKeys: PropTypes.oneOfType([
-      PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
-      PropTypes.object,
-    ]),
-    defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
-    selectedKeys: PropTypes.arrayOf(PropTypes.string),
+    defaultExpandedKeys: PropTypes.arrayOf(keyPropType),
+    expandedKeys: PropTypes.arrayOf(keyPropType),
+    defaultCheckedKeys: PropTypes.arrayOf(keyPropType),
+    checkedKeys: PropTypes.oneOfType([PropTypes.arrayOf(keyPropType), PropTypes.object]),
+    defaultSelectedKeys: PropTypes.arrayOf(keyPropType),
+    selectedKeys: PropTypes.arrayOf(keyPropType),
     onClick: PropTypes.func,
     onDoubleClick: PropTypes.func,
     onExpand: PropTypes.func,
@@ -205,7 +204,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     onSelect: PropTypes.func,
     onLoad: PropTypes.func,
     loadData: PropTypes.func,
-    loadedKeys: PropTypes.arrayOf(PropTypes.string),
+    loadedKeys: PropTypes.arrayOf(keyPropType),
     onMouseEnter: PropTypes.func,
     onMouseLeave: PropTypes.func,
     onRightClick: PropTypes.func,

From 1b43b996afb0b81db2db2b97e8da545b7b5953da Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 19 Sep 2019 14:31:37 +0800
Subject: [PATCH 041/397] v3.0.0-alpha.33

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b04b4c1c..d11d3178 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.32",
+  "version": "3.0.0-alpha.33",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 2ffa933527c17b28ba7c6711ccb1f7af023f3fc2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Fri, 20 Sep 2019 16:47:26 +0800
Subject: [PATCH 042/397] fix missing logic (#277)

---
 examples/funtionTitle.jsx |  22 ++--
 src/Tree.tsx              |  23 +++-
 src/util.tsx              | 147 ------------------------
 src/utils/conductUtil.ts  | 235 ++++++++++++++++++++++++++++++++++++++
 tests/util.spec.js        |  27 +++--
 5 files changed, 286 insertions(+), 168 deletions(-)
 create mode 100644 src/utils/conductUtil.ts

diff --git a/examples/funtionTitle.jsx b/examples/funtionTitle.jsx
index 0f8283cf..5a150941 100644
--- a/examples/funtionTitle.jsx
+++ b/examples/funtionTitle.jsx
@@ -25,11 +25,9 @@ const motion = {
   onLeaveActive: () => ({ height: 0 }),
 };
 
-const renderTitle = title => {
-  console.log('run');
-  return title;
-};
-
+const renderTitle = title =>
+  // console.log('run');
+  title;
 const groupList = (list, targetVar) => {
   const obj = {};
   list.forEach(item => {
@@ -43,7 +41,7 @@ const groupList = (list, targetVar) => {
       disabled,
     });
   });
-  console.log(obj);
+  // console.log(obj);
   return (
     Object.keys(obj)
       .map(key => ({
@@ -56,6 +54,11 @@ const groupList = (list, targetVar) => {
 };
 
 function getTreeData() {
+  // return [
+  //   { key: '00', children: [{ key: '000' }, { key: '001' }] },
+  //   { key: '01', children: [{ key: '010' }, { key: '011' }] },
+  // ];
+
   return groupList(
     data.map(item => ({
       title: () => renderTitle(item.fieldName),
@@ -70,6 +73,8 @@ function getTreeData() {
 
 const Demo = () => {
   const [keys, setKeys] = useState(data.map(item => item.fieldName));
+  // const [keys, setKeys] = useState(['00', '01']);
+
   return (
     <div className="animation">
       <h2>expanded</h2>
@@ -85,7 +90,10 @@ const Demo = () => {
             height={200}
             checkedKeys={keys}
             itemHeight={20}
-            onCheck={checkedKeys => setKeys(checkedKeys)}
+            onCheck={checkedKeys => {
+              console.log('onCheck:', checkedKeys);
+              setKeys(checkedKeys);
+            }}
             style={{ border: '1px solid #000' }}
             treeData={getTreeData()}
           />
diff --git a/src/Tree.tsx b/src/Tree.tsx
index ba59e133..e30472b1 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -19,7 +19,6 @@ import {
   arrAdd,
   arrDel,
   posToArr,
-  conductCheck,
 } from './util';
 import {
   DataNode,
@@ -41,6 +40,7 @@ import {
 } from './utils/treeUtil';
 import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
 import TreeNode from './TreeNode';
+import { conductCheck } from './utils/conductUtil';
 
 interface CheckInfo {
   event: 'check';
@@ -647,10 +647,23 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
       this.setUncontrolledState({ checkedKeys });
     } else {
-      const { checkedKeys, halfCheckedKeys } = conductCheck([key], checked, keyEntities, {
-        checkedKeys: oriCheckedKeys,
-        halfCheckedKeys: oriHalfCheckedKeys,
-      });
+      // Always fill first
+      let { checkedKeys, halfCheckedKeys } = conductCheck(
+        [...oriCheckedKeys, key],
+        true,
+        keyEntities,
+      );
+
+      // If remove, we do it again to correction
+      if (!checked) {
+        const keySet = new Set(checkedKeys);
+        keySet.delete(key);
+        ({ checkedKeys, halfCheckedKeys } = conductCheck(
+          Array.from(keySet),
+          { checked: false, halfCheckedKeys },
+          keyEntities,
+        ));
+      }
 
       checkedObj = checkedKeys;
 
diff --git a/src/util.tsx b/src/util.tsx
index d0e17afc..76130eb4 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -150,153 +150,6 @@ export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Ke
   return keyProps;
 }
 
-/**
- * Conduct check state by the keyList. It will conduct up & from the provided key.
- * If the conduct path reach the disabled or already checked / unchecked node will stop conduct.
- */
-export function conductCheck(
-  /** list of keys */
-  keyList: Key[],
-  /** is check the node or not */
-  isCheck: boolean,
-  /** parsed by `convertTreeToEntities` function in Tree */
-  keyEntities: Record<Key, DataEntity>,
-  /** Can pass current checked status for process (usually for uncheck operation) */
-  checkStatus: { checkedKeys?: Key[]; halfCheckedKeys?: Key[] } = {},
-) {
-  const warningMissKeys: Key[] = [];
-
-  const checkedKeyMap: Map<Key, boolean> = new Map();
-  // Record the key has some child checked (include child half checked)
-  const halfCheckedKeyMap: Map<Key, boolean> = new Map();
-
-  (checkStatus.checkedKeys || []).forEach(key => {
-    checkedKeyMap.set(key, true);
-  });
-
-  (checkStatus.halfCheckedKeys || []).forEach(key => {
-    halfCheckedKeyMap.set(key, true);
-  });
-
-  // Conduct up
-  function conductUp(key: Key) {
-    if (checkedKeyMap.get(key) === isCheck) return;
-
-    const entity = keyEntities[key];
-    if (!entity) return;
-
-    const { children, parent, node } = entity;
-
-    if (isCheckDisabled(node)) return;
-
-    // Check child node checked status
-    let everyChildChecked = true;
-    let someChildChecked = false; // Child checked or half checked
-
-    if (children && children.length) {
-      children.forEach(({ key: childKey, node: childNode }) => {
-        if (isCheckDisabled(childNode)) return;
-
-        const childChecked = checkedKeyMap.get(childKey);
-        const childHalfChecked = halfCheckedKeyMap.get(childKey);
-
-        if (childChecked || childHalfChecked) someChildChecked = true;
-        if (!childChecked) everyChildChecked = false;
-      });
-    }
-
-    // Update checked status
-    if (isCheck) {
-      checkedKeyMap.set(key, everyChildChecked);
-    } else {
-      checkedKeyMap.set(key, false);
-    }
-    halfCheckedKeyMap.set(key, someChildChecked);
-
-    if (parent) {
-      conductUp(parent.key);
-    }
-  }
-
-  // Conduct down
-  function conductDown(key: Key) {
-    if (checkedKeyMap.get(key) === isCheck) return;
-
-    const entity = keyEntities[key];
-    if (!entity) return;
-
-    const { children, node } = entity;
-
-    if (isCheckDisabled(node)) return;
-
-    checkedKeyMap.set(key, isCheck);
-
-    (children || []).forEach(child => {
-      conductDown(child.key);
-    });
-  }
-
-  function conduct(key: Key) {
-    const entity = keyEntities[key];
-
-    if (!entity) {
-      warningMissKeys.push(key);
-      return;
-    }
-
-    const { children, parent, node } = entity;
-    checkedKeyMap.set(key, isCheck);
-
-    if (isCheckDisabled(node)) return;
-
-    // Conduct down
-    (children || [])
-      .filter(child => !isCheckDisabled(child.node))
-      .forEach(child => {
-        conductDown(child.key);
-      });
-
-    // Conduct up
-    if (parent) {
-      conductUp(parent.key);
-    }
-  }
-
-  (keyList || []).forEach(key => {
-    conduct(key);
-  });
-
-  const checkedKeyList = [];
-  const halfCheckedKeyList = [];
-
-  // Fill checked list
-  checkedKeyMap.forEach((checked, key) => {
-    if (checked) {
-      checkedKeyList.push(key);
-    }
-  });
-
-  // Fill half checked list
-  halfCheckedKeyMap.forEach((halfChecked, key) => {
-    if (!checkedKeyMap.get(key) && halfChecked) {
-      halfCheckedKeyList.push(key);
-    }
-  });
-
-  warning(
-    !warningMissKeys.length,
-    `Tree missing follow keys: ${warningMissKeys
-      .slice(0, 100)
-      .map(key => `'${key}'`)
-      .join(', ')}`,
-  );
-
-  return {
-    checkedKeys: checkedKeyList,
-    halfCheckedKeys: halfCheckedKeyList,
-  };
-}
-
 /**
  * If user use `autoExpandParent` we should get the list of parent node
  * @param keyList
diff --git a/src/utils/conductUtil.ts b/src/utils/conductUtil.ts
new file mode 100644
index 00000000..94026d86
--- /dev/null
+++ b/src/utils/conductUtil.ts
@@ -0,0 +1,235 @@
+import warning from 'rc-util/lib/warning';
+import { Key, DataEntity, DataNode } from '../interface';
+
+interface ConductReturnType {
+  checkedKeys: Key[];
+  halfCheckedKeys: Key[];
+}
+
+function removeFromCheckedKeys(halfCheckedKeys: Set<Key>, checkedKeys: Set<Key>) {
+  const filteredKeys = new Set<Key>();
+  halfCheckedKeys.forEach(key => {
+    if (!checkedKeys.has(key)) {
+      filteredKeys.add(key);
+    }
+  });
+  return filteredKeys;
+}
+
+export function isCheckDisabled(node: DataNode) {
+  const { disabled, disableCheckbox, checkable } = (node || {}) as DataNode;
+  return !!(disabled || disableCheckbox) || checkable === false;
+}
+
+// Fill miss keys
+function fillConductCheck(
+  keys: Set<Key>,
+  levelEntities: Map<number, Set<DataEntity>>,
+  maxLevel: number,
+): ConductReturnType {
+  const checkedKeys = new Set<Key>(keys);
+  const halfCheckedKeys = new Set<Key>();
+
+  // Add checked keys top to bottom
+  for (let level = 0; level <= maxLevel; level += 1) {
+    const entities = levelEntities.get(level) || new Set();
+    entities.forEach(entity => {
+      const { key, node, children = [] } = entity;
+
+      if (checkedKeys.has(key) && !isCheckDisabled(node)) {
+        children
+          .filter(childEntity => !isCheckDisabled(childEntity.node))
+          .forEach(childEntity => {
+            checkedKeys.add(childEntity.key);
+          });
+      }
+    });
+  }
+
+  // Add checked keys from bottom to top
+  const visitedKeys = new Set<Key>();
+  for (let level = maxLevel; level >= 0; level -= 1) {
+    const entities = levelEntities.get(level) || new Set();
+    entities.forEach(entity => {
+      const { parent, node } = entity;
+
+      // Skip if no need to check
+      if (isCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
+        return;
+      }
+
+      // Skip if parent is disabled
+      if (isCheckDisabled(entity.parent.node)) {
+        visitedKeys.add(parent.key);
+        return;
+      }
+
+      let allChecked = true;
+      let partialChecked = false;
+
+      (parent.children || [])
+        .filter(childEntity => !isCheckDisabled(childEntity.node))
+        .forEach(({ key }) => {
+          const checked = checkedKeys.has(key);
+          if (allChecked && !checked) {
+            allChecked = false;
+          }
+          if (!partialChecked && (checked || halfCheckedKeys.has(key))) {
+            partialChecked = true;
+          }
+        });
+
+      if (allChecked) {
+        checkedKeys.add(parent.key);
+      }
+      if (partialChecked) {
+        halfCheckedKeys.add(parent.key);
+      }
+
+      visitedKeys.add(parent.key);
+    });
+  }
+
+  return {
+    checkedKeys: Array.from(checkedKeys),
+    halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys)),
+  };
+}
+
+// Remove useless key
+function cleanConductCheck(
+  keys: Set<Key>,
+  halfKeys: Key[],
+  levelEntities: Map<number, Set<DataEntity>>,
+  maxLevel: number,
+): ConductReturnType {
+  const checkedKeys = new Set<Key>(keys);
+  const halfCheckedKeys = new Set<Key>(halfKeys);
+
+  // Remove checked keys from top to bottom
+  for (let level = 0; level <= maxLevel; level += 1) {
+    const entities = levelEntities.get(level) || new Set();
+    entities.forEach(entity => {
+      const { key, node, children = [] } = entity;
+
+      if (!checkedKeys.has(key) && !halfCheckedKeys.has(key) && !isCheckDisabled(node)) {
+        children
+          .filter(childEntity => !isCheckDisabled(childEntity.node))
+          .forEach(childEntity => {
+            checkedKeys.delete(childEntity.key);
+          });
+      }
+    });
+  }
+
+  // Remove checked keys form bottom to top
+  const visitedKeys = new Set<Key>();
+  for (let level = maxLevel; level >= 0; level -= 1) {
+    const entities = levelEntities.get(level) || new Set();
+
+    entities.forEach(entity => {
+      const { parent, node } = entity;
+
+      // Skip if no need to check
+      if (isCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
+        return;
+      }
+
+      // Skip if parent is disabled
+      if (isCheckDisabled(entity.parent.node)) {
+        visitedKeys.add(parent.key);
+        return;
+      }
+
+      let allChecked = true;
+      let partialChecked = false;
+
+      (parent.children || [])
+        .filter(childEntity => !isCheckDisabled(childEntity.node))
+        .forEach(({ key }) => {
+          const checked = checkedKeys.has(key);
+          if (allChecked && !checked) {
+            allChecked = false;
+          }
+          if (!partialChecked && (checked || halfCheckedKeys.has(key))) {
+            partialChecked = true;
+          }
+        });
+
+      if (!allChecked) {
+        checkedKeys.delete(parent.key);
+      }
+      if (partialChecked) {
+        halfCheckedKeys.add(parent.key);
+      }
+
+      visitedKeys.add(parent.key);
+    });
+  }
+
+  return {
+    checkedKeys: Array.from(checkedKeys),
+    halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys)),
+  };
+}
+
+/**
+ * Conduct with keys.
+ * @param keyList current key list
+ * @param keyEntities key - dataEntity map
+ * @param mode `fill` to fill missing key, `clean` to remove useless key
+ */
+export function conductCheck(
+  keyList: Key[],
+  checked: true | { checked: false; halfCheckedKeys: Key[] },
+  keyEntities: Record<Key, DataEntity>,
+): ConductReturnType {
+  const warningMissKeys: Key[] = [];
+
+  // We only handle exist keys
+  const keys = new Set<Key>(
+    keyList.filter(key => {
+      const hasEntity = !!keyEntities[key];
+      if (!hasEntity) {
+        warningMissKeys.push(key);
+      }
+
+      return hasEntity;
+    }),
+  );
+  const levelEntities = new Map<number, Set<DataEntity>>();
+  let maxLevel = 0;
+
+  // Convert entities by level for calculation
+  Object.keys(keyEntities).forEach(key => {
+    const entity = keyEntities[key];
+    const { level } = entity;
+
+    let levelSet: Set<DataEntity> = levelEntities.get(level);
+    if (!levelSet) {
+      levelSet = new Set();
+      levelEntities.set(level, levelSet);
+    }
+
+    levelSet.add(entity);
+
+    maxLevel = Math.max(maxLevel, level);
+  });
+
+  warning(
+    !warningMissKeys.length,
+    `Tree missing follow keys: ${warningMissKeys
+      .slice(0, 100)
+      .map(key => `'${key}'`)
+      .join(', ')}`,
+  );
+
+  let result: ConductReturnType;
+  if (checked === true) {
+    result = fillConductCheck(keys, levelEntities, maxLevel);
+  } else {
+    result = cleanConductCheck(keys, checked.halfCheckedKeys, levelEntities, maxLevel);
+  }
+
+  return result;
+}
diff --git a/tests/util.spec.js b/tests/util.spec.js
index e250b479..ed9f3edb 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -4,7 +4,6 @@ import React from 'react';
 import Tree, { TreeNode } from '../src';
 import {
   convertDataToTree,
-  conductCheck,
   conductExpandParent,
   getDragNodesKeys,
   getDataAndAria,
@@ -17,6 +16,7 @@ import {
   getTreeNodeProps,
 } from '../src/utils/treeUtil';
 import { spyConsole, spyError } from './util';
+import { conductCheck } from '../src/utils/conductUtil';
 
 describe('Util', () => {
   spyConsole();
@@ -183,10 +183,13 @@ describe('Util', () => {
         const allCheckedKeys = conductCheck(['greed', 'good'], true, keyEntities).checkedKeys;
         expect(allCheckedKeys.length).toEqual(11);
 
-        // Then uncheck one of then
-        const { checkedKeys, halfCheckedKeys } = conductCheck(['spoon'], false, keyEntities, {
-          checkedKeys: allCheckedKeys,
-        });
+        // Then un-check one of then
+        const removedKeys = allCheckedKeys.filter(key => key !== 'spoon');
+        const { checkedKeys, halfCheckedKeys } = conductCheck(
+          removedKeys,
+          { checked: false, halfCheckedKeys: [] },
+          keyEntities,
+        );
         expect(checkedKeys.sort()).toEqual(['greed', 'there', 'no'].sort());
         expect(halfCheckedKeys.sort()).toEqual(['good', 'is'].sort());
       });
@@ -243,13 +246,19 @@ describe('Util', () => {
         expect(allCheckedKeys.length).toEqual(6);
 
         // Then uncheck one of then
-        const result1 = conductCheck(['not'], false, keyEntities, { checkedKeys: allCheckedKeys });
+        const result1 = conductCheck(
+          allCheckedKeys.filter(key => key !== 'not'),
+          { checked: false, halfCheckedKeys: [] },
+          keyEntities,
+        );
         expect(result1.checkedKeys.sort()).toEqual(['are', 'to', 'be'].sort());
         expect(result1.halfCheckedKeys.sort()).toEqual(['war'].sort());
 
-        const result2 = conductCheck(['to', 'be'], false, keyEntities, {
-          checkedKeys: allCheckedKeys,
-        });
+        const result2 = conductCheck(
+          allCheckedKeys.filter(key => key !== 'to' && key !== 'be'),
+          { checked: false, halfCheckedKeys: [] },
+          keyEntities,
+        );
         expect(result2.checkedKeys.sort()).toEqual(['war', 'are', 'not', 'it'].sort());
         expect(result2.halfCheckedKeys.sort()).toEqual([].sort());
       });

From a045a1ddd2b82d621b79f59db0fa5123474b41bc Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 20 Sep 2019 16:49:31 +0800
Subject: [PATCH 043/397] v3.0.0-alpha.34

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d11d3178..55dc9810 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.33",
+  "version": "3.0.0-alpha.34",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 83f20d5f85760aaea0e8ef6f2cda1006c844c3f4 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 25 Sep 2019 14:35:30 +0800
Subject: [PATCH 044/397] fix: expandedKeys is null should not crash

---
 src/Tree.tsx       | 12 ++++++------
 src/util.tsx       |  5 -----
 tests/Tree.spec.js |  8 ++++++++
 3 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index e30472b1..61f7c95c 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -838,12 +838,12 @@ class Tree extends React.Component<TreeProps, TreeState> {
       keyEntities,
     } = this.state;
     return {
-      expandedKeys,
-      selectedKeys,
-      loadedKeys,
-      loadingKeys,
-      checkedKeys,
-      halfCheckedKeys,
+      expandedKeys: expandedKeys || [],
+      selectedKeys: selectedKeys || [],
+      loadedKeys: loadedKeys || [],
+      loadingKeys: loadingKeys || [],
+      checkedKeys: checkedKeys || [],
+      halfCheckedKeys: halfCheckedKeys || [],
       dragOverNodeKey,
       dropPosition,
       keyEntities,
diff --git a/src/util.tsx b/src/util.tsx
index 76130eb4..6f06d794 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -40,11 +40,6 @@ export function isTreeNode(node: NodeElement) {
   return node && node.type && node.type.isTreeNode;
 }
 
-export function isCheckDisabled(node: DataNode) {
-  const { disabled, disableCheckbox, checkable } = (node || {}) as DataNode;
-  return !!(disabled || disableCheckbox) || checkable === false;
-}
-
 export function getDragNodesKeys(dragNodeKey: Key, keyEntities: Record<Key, DataEntity>): Key[] {
   const dragNodesKeys = [dragNodeKey];
 
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index b4060197..e1cc4876 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1323,4 +1323,12 @@ describe('Tree Basic', () => {
 
     expect(scrollTo).toHaveBeenCalled();
   });
+
+  it('not crash if expandedKeys is null', () => {
+    mount(
+      <Tree expandedKeys={null}>
+        <TreeNode key="test" title="alive" />
+      </Tree>,
+    );
+  });
 });

From ffdabafe9ab1c7e82d7a23fcc969da42ab043005 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 25 Sep 2019 14:36:05 +0800
Subject: [PATCH 045/397] v3.0.0-alpha.35

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 55dc9810..c111c898 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.34",
+  "version": "3.0.0-alpha.35",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From dbbdef0c16060384712f874b27772646a3fb185b Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 27 Sep 2019 17:04:46 +0800
Subject: [PATCH 046/397] fix: Tree should not over height if content is less
 than height

---
 src/NodeList.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 64de9348..dbf1864b 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -276,6 +276,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         data={mergedData}
         itemKey={itemKey}
         height={height}
+        fullHeight={false}
         itemHeight={itemHeight}
         onSkipRender={onMotionEnd}
         prefixCls={`${prefixCls}-list`}

From c8965bfa89b200e133996269afc2dff7b8264c3b Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 27 Sep 2019 17:05:22 +0800
Subject: [PATCH 047/397] v3.0.0-alpha.36

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c111c898..257d5e40 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.35",
+  "version": "3.0.0-alpha.36",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 92303f0181d1c554f474bb9df53aa90bf5adcaeb Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sun, 29 Sep 2019 11:13:56 +0800
Subject: [PATCH 048/397] fix: TreeNode crash when motion support

---
 src/NodeList.tsx                              |   1 -
 src/Tree.tsx                                  |  49 +--
 src/TreeNode.tsx                              |   4 +-
 tests/TreeMotion.spec.js                      |  12 +
 tests/__snapshots__/Tree.spec.js.snap         |  84 +++--
 .../__snapshots__/TreeNodeProps.spec.js.snap  | 108 ++++---
 tests/__snapshots__/TreeProps.spec.js.snap    | 300 ++++++++++--------
 7 files changed, 327 insertions(+), 231 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index dbf1864b..428c43fa 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -52,7 +52,6 @@ export interface NodeListRef {
 
 interface NodeListProps {
   prefixCls: string;
-  className: string;
   style: React.CSSProperties;
   data: FlattenNode[];
   motion: any;
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 61f7c95c..42fe20c4 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -1062,34 +1062,37 @@ class Tree extends React.Component<TreeProps, TreeState> {
           onNodeDrop: this.onNodeDrop,
         }}
       >
-        <NodeList
-          ref={this.listRef}
+        <div
           className={classNames(prefixCls, className, {
             [`${prefixCls}-show-line`]: showLine,
             [`${prefixCls}-focused`]: focused,
             [`${prefixCls}-active-focused`]: activeKey !== null,
           })}
-          prefixCls={prefixCls}
-          style={style}
-          data={flattenNodes}
-          disabled={disabled}
-          selectable={selectable}
-          checkable={!!checkable}
-          motion={motion}
-          dragging={dragging}
-          height={height}
-          itemHeight={itemHeight}
-          focusable={focusable}
-          focused={focused}
-          tabIndex={tabIndex}
-          activeItem={this.getActiveItem()}
-          onFocus={this.onFocus}
-          onBlur={this.onBlur}
-          onKeyDown={this.onKeyDown}
-          onActiveChange={this.onActiveChange}
-          {...this.getTreeNodeRequiredProps()}
-          {...domProps}
-        />
+        >
+          <NodeList
+            ref={this.listRef}
+            prefixCls={prefixCls}
+            style={style}
+            data={flattenNodes}
+            disabled={disabled}
+            selectable={selectable}
+            checkable={!!checkable}
+            motion={motion}
+            dragging={dragging}
+            height={height}
+            itemHeight={itemHeight}
+            focusable={focusable}
+            focused={focused}
+            tabIndex={tabIndex}
+            activeItem={this.getActiveItem()}
+            onFocus={this.onFocus}
+            onBlur={this.onBlur}
+            onKeyDown={this.onKeyDown}
+            onActiveChange={this.onActiveChange}
+            {...this.getTreeNodeRequiredProps()}
+            {...domProps}
+          />
+        </div>
       </TreeContext.Provider>
     );
   }
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 32cc3c3e..4903fbfa 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -274,7 +274,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const {
       context: { keyEntities },
     } = this.props;
-    const { children } = keyEntities[eventKey];
+    const { children } = keyEntities[eventKey] || {};
 
     return !!(children || []).length;
   };
@@ -511,7 +511,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     } = this.props;
     const disabled = this.isDisabled();
     const dataOrAriaAttributeProps = getDataAndAria(otherProps);
-    const { level } = keyEntities[eventKey];
+    const { level } = keyEntities[eventKey] || {};
 
     return (
       <div
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.js
index 52b8eae5..c5df3ef1 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.js
@@ -58,4 +58,16 @@ describe('Tree Motion', () => {
 
     expect(visibleList.length < 10).toBeTruthy();
   });
+
+  it('not crash', () => {
+    const wrapper = mount(
+      <Tree
+        treeData={[{ key: '0-0', children: [{ key: '0-0-0' }] }]}
+        expandedKeys={['0-0']}
+        motion={{}}
+      />,
+    );
+
+    wrapper.setProps({ treeData: [] });
+  });
 });
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index 4e3b86d5..d5d6fb62 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -1,7 +1,9 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`Tree Basic check basic render 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -10,9 +12,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -106,12 +108,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Basic check check after data ready works 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -120,9 +124,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -185,12 +189,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Basic check check update when Tree trigger componentWillReceiveProps 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -199,9 +205,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -233,12 +239,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -247,9 +255,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -298,12 +306,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -312,9 +322,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -419,12 +429,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Basic renders correctly 1`] = `
-Array [
+<div
+  class="rc-tree forTest rc-tree-show-line"
+>
   <div
     role="tree"
   >
@@ -433,9 +445,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree forTest rc-tree-show-line"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -597,12 +609,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Basic renders opaque children correctly 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -611,9 +625,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -642,6 +656,6 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index 5e6e8b90..bbd5a326 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -1,7 +1,9 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`TreeNode Props className 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -10,9 +12,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -148,12 +150,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`TreeNode Props customize icon component 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -162,9 +166,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -197,12 +201,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`TreeNode Props customize icon element 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -211,9 +217,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -246,12 +252,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`TreeNode Props customize icon hide icon 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -260,9 +268,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -288,12 +296,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`TreeNode Props data and aria props renders aria attributes on li 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -302,9 +312,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -445,12 +455,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`TreeNode Props data and aria props renders data attributes on li 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -459,9 +471,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -602,12 +614,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`TreeNode Props isLeaf 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -616,9 +630,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -669,12 +683,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`TreeNode Props isLeaf 2`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -683,9 +699,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -742,12 +758,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`TreeNode Props isLeaf 3`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -756,9 +774,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -815,6 +833,6 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index b0b2c482..b6d8ec19 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -1,7 +1,9 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`Tree Props checkStrictly 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -10,9 +12,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -75,12 +77,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props checkable default 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -89,9 +93,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -154,12 +158,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props checkable without selectable 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -168,9 +174,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -233,12 +239,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props custom switcher icon switcher icon 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -247,9 +255,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -358,12 +366,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -372,9 +382,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -539,12 +549,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props data and aria props renders aria attributes 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -553,10 +565,10 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
     aria-label="name"
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -564,12 +576,14 @@ Array [
         style="display:flex;flex-direction:column"
       />
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props data and aria props renders data attributes 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -578,9 +592,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
     data-test="tree"
   >
     <div>
@@ -589,12 +603,14 @@ Array [
         style="display:flex;flex-direction:column"
       />
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props defaultExpandAll 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -603,9 +619,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -662,12 +678,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props disabled basic 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -677,9 +695,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -708,12 +726,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -723,9 +743,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -754,12 +774,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props icon 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -768,9 +790,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -835,12 +857,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props invalidate checkedKeys null 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -849,9 +873,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -914,12 +938,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props invalidate checkedKeys number 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -928,9 +954,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -993,12 +1019,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props loadData basic 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1007,9 +1035,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1060,12 +1088,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props multiple 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1074,9 +1104,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1133,12 +1163,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props prefixCls 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1147,9 +1179,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1157,12 +1189,14 @@ Array [
         style="display:flex;flex-direction:column"
       />
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props prefixCls 2`] = `
-Array [
+<div
+  class="test-prefix"
+>
   <div
     role="tree"
   >
@@ -1171,9 +1205,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="test-prefix-list test-prefix"
+    class="test-prefix-list"
   >
     <div>
       <div
@@ -1181,12 +1215,14 @@ Array [
         style="display:flex;flex-direction:column"
       />
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props selectable with selectable 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1195,9 +1231,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1254,12 +1290,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props selectable without selectable 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1268,9 +1306,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1327,12 +1365,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props showIcon 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1341,9 +1381,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1392,12 +1432,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props showIcon 2`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1406,9 +1448,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1451,12 +1493,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props showIcon 3`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1465,9 +1509,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1611,12 +1655,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props showLine 1`] = `
-Array [
+<div
+  class="rc-tree rc-tree-show-line"
+>
   <div
     role="tree"
   >
@@ -1625,9 +1671,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree rc-tree-show-line"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1635,12 +1681,14 @@ Array [
         style="display:flex;flex-direction:column"
       />
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props treeData 1`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1649,9 +1697,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1846,12 +1894,14 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;
 
 exports[`Tree Props treeData 2`] = `
-Array [
+<div
+  class="rc-tree"
+>
   <div
     role="tree"
   >
@@ -1860,9 +1910,9 @@ Array [
       tabindex="0"
       value=""
     />
-  </div>,
+  </div>
   <div
-    class="rc-tree-list rc-tree"
+    class="rc-tree-list"
   >
     <div>
       <div
@@ -1891,6 +1941,6 @@ Array [
         </div>
       </div>
     </div>
-  </div>,
-]
+  </div>
+</div>
 `;

From e07dcab71bf1af4eaac4d34a52c26aed0043958d Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sun, 29 Sep 2019 11:14:39 +0800
Subject: [PATCH 049/397] v3.0.0-alpha.37

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 257d5e40..e9b954fc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.36",
+  "version": "3.0.0-alpha.37",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 2b36e4b2545cc0d0d118ba14c25e784378edd3a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E5=B8=85?= <qixian.cs@outlook.com>
Date: Sun, 17 Nov 2019 19:19:11 +0800
Subject: [PATCH 050/397] up storybook version

---
 package.json | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/package.json b/package.json
index e9b954fc..1a645e55 100644
--- a/package.json
+++ b/package.json
@@ -50,10 +50,9 @@
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",
     "enzyme-to-json": "^3.0.0",
-    "father": "^2.13.2",
+    "father": "^2.25.0-alpha.1",
     "np": "^5.0.3",
     "rc-dialog": "^7.3.1",
-    "rc-tools": "^9.x",
     "rc-tooltip": "3.x",
     "rc-trigger": "^2.0.0",
     "react": "^16.8.0",

From 0ff4afa72cd724c1d68428f19541c92ce05b7144 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 24 Dec 2019 15:39:31 +0800
Subject: [PATCH 051/397] fix: Not crash when expandedKeys change to undefined
 (#284)

---
 .eslintrc.js       |   1 +
 .prettierrc        |   7 ++
 src/Tree.tsx       | 182 ++++++++++++++++++++++++++++++++++++---------
 tests/Tree.spec.js | 143 +++++++++++++++++++++++++++--------
 4 files changed, 267 insertions(+), 66 deletions(-)
 create mode 100644 .prettierrc

diff --git a/.eslintrc.js b/.eslintrc.js
index 5fd84791..902cd574 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -4,6 +4,7 @@ module.exports = {
   ...base,
   rules: {
     ...base.rules,
+    'react/sort-comp': 0,
     'default-case': 0,
     'eslint-comments/disable-enable-pair': 0,
     'react/require-default-props': 0,
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..27dd8afb
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+  "endOfLine": "lf",
+  "semi": true,
+  "singleQuote": true,
+  "tabWidth": 2,
+  "trailingComma": "all"
+}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 42fe20c4..c01cdb16 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -91,7 +91,10 @@ export interface TreeProps {
       nativeEvent: MouseEvent;
     },
   ) => void;
-  onCheck?: (checked: { checked: Key[]; halfChecked: Key[] } | Key[], info: CheckInfo) => void;
+  onCheck?: (
+    checked: { checked: Key[]; halfChecked: Key[] } | Key[],
+    info: CheckInfo,
+  ) => void;
   onSelect?: (
     selectedKeys: Key[],
     info: {
@@ -111,17 +114,32 @@ export interface TreeProps {
   ) => void;
   loadData?: (treeNode: EventDataNode) => Promise<void>;
   loadedKeys?: Key[];
-  onMouseEnter?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onMouseLeave?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onRightClick?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onDragStart?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onMouseEnter?: (info: {
+    event: React.MouseEvent;
+    node: EventDataNode;
+  }) => void;
+  onMouseLeave?: (info: {
+    event: React.MouseEvent;
+    node: EventDataNode;
+  }) => void;
+  onRightClick?: (info: {
+    event: React.MouseEvent;
+    node: EventDataNode;
+  }) => void;
+  onDragStart?: (info: {
+    event: React.MouseEvent;
+    node: EventDataNode;
+  }) => void;
   onDragEnter?: (info: {
     event: React.MouseEvent;
     node: EventDataNode;
     expandedKeys: Key[];
   }) => void;
   onDragOver?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onDragLeave?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onDragLeave?: (info: {
+    event: React.MouseEvent;
+    node: EventDataNode;
+  }) => void;
   onDragEnd?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
   onDrop?: (info: {
     event: React.MouseEvent;
@@ -194,7 +212,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     defaultExpandedKeys: PropTypes.arrayOf(keyPropType),
     expandedKeys: PropTypes.arrayOf(keyPropType),
     defaultCheckedKeys: PropTypes.arrayOf(keyPropType),
-    checkedKeys: PropTypes.oneOfType([PropTypes.arrayOf(keyPropType), PropTypes.object]),
+    checkedKeys: PropTypes.oneOfType([
+      PropTypes.arrayOf(keyPropType),
+      PropTypes.object,
+    ]),
     defaultSelectedKeys: PropTypes.arrayOf(keyPropType),
     selectedKeys: PropTypes.arrayOf(keyPropType),
     onClick: PropTypes.func,
@@ -276,7 +297,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     };
 
     function needSync(name: string) {
-      return (!prevProps && name in props) || (prevProps && prevProps[name] !== props[name]);
+      return (
+        (!prevProps && name in props) ||
+        (prevProps && prevProps[name] !== props[name])
+      );
     }
 
     // ================== Tree Node ==================
@@ -286,7 +310,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     if (needSync('treeData')) {
       ({ treeData } = props);
     } else if (needSync('children')) {
-      warning(false, '`children` of Tree is deprecated. Please use `treeData` instead.');
+      warning(
+        false,
+        '`children` of Tree is deprecated. Please use `treeData` instead.',
+      );
       treeData = convertTreeToData(props.children);
     }
 
@@ -308,7 +335,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     const keyEntities = newState.keyEntities || prevState.keyEntities;
 
     // ================ expandedKeys =================
-    if (needSync('expandedKeys') || (prevProps && needSync('autoExpandParent'))) {
+    if (
+      needSync('expandedKeys') ||
+      (prevProps && needSync('autoExpandParent'))
+    ) {
       newState.expandedKeys =
         props.autoExpandParent || (!prevProps && props.defaultExpandParent)
           ? conductExpandParent(props.expandedKeys, keyEntities)
@@ -316,7 +346,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
     } else if (!prevProps && props.defaultExpandAll) {
       const cloneKeyEntities = { ...keyEntities };
       delete cloneKeyEntities[MOTION_KEY];
-      newState.expandedKeys = Object.keys(cloneKeyEntities).map(key => cloneKeyEntities[key].key);
+      newState.expandedKeys = Object.keys(cloneKeyEntities).map(
+        key => cloneKeyEntities[key].key,
+      );
     } else if (!prevProps && props.defaultExpandedKeys) {
       newState.expandedKeys =
         props.autoExpandParent || props.defaultExpandParent
@@ -324,6 +356,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
           : props.defaultExpandedKeys;
     }
 
+    if (!newState.expandedKeys) {
+      delete newState.expandedKeys;
+    }
+
     // ================ flattenNodes =================
     if (treeData || newState.expandedKeys) {
       const flattenNodes: FlattenNode[] = flattenTreeData(
@@ -338,7 +374,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
       if (needSync('selectedKeys')) {
         newState.selectedKeys = calcSelectedKeys(props.selectedKeys, props);
       } else if (!prevProps && props.defaultSelectedKeys) {
-        newState.selectedKeys = calcSelectedKeys(props.defaultSelectedKeys, props);
+        newState.selectedKeys = calcSelectedKeys(
+          props.defaultSelectedKeys,
+          props,
+        );
       }
     }
 
@@ -379,7 +418,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return newState;
   }
 
-  onNodeDragStart = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragStart = (
+    event: React.MouseEvent<HTMLDivElement>,
+    node: NodeInstance,
+  ) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
     const { eventKey } = node.props;
@@ -404,7 +446,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
    * Better for use mouse move event to refresh drag state.
    * But let's just keep it to avoid event trigger logic change.
    */
-  onNodeDragEnter = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragEnter = (
+    event: React.MouseEvent<HTMLDivElement>,
+    node: NodeInstance,
+  ) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragEnter } = this.props;
     const { pos, eventKey } = node.props;
@@ -468,7 +513,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }, 0);
   };
 
-  onNodeDragOver = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragOver = (
+    event: React.MouseEvent<HTMLDivElement>,
+    node: NodeInstance,
+  ) => {
     const { onDragOver } = this.props;
     const { eventKey } = node.props;
 
@@ -488,7 +536,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeDragLeave = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragLeave = (
+    event: React.MouseEvent<HTMLDivElement>,
+    node: NodeInstance,
+  ) => {
     const { onDragLeave } = this.props;
 
     this.setState({
@@ -500,7 +551,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeDragEnd = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragEnd = (
+    event: React.MouseEvent<HTMLDivElement>,
+    node: NodeInstance,
+  ) => {
     const { onDragEnd } = this.props;
     this.setState({
       dragOverNodeKey: '',
@@ -514,7 +568,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     this.dragNode = null;
   };
 
-  onNodeDrop = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDrop = (
+    event: React.MouseEvent<HTMLDivElement>,
+    node: NodeInstance,
+  ) => {
     const { dragNodesKeys = [], dropPosition } = this.state;
     const { onDrop } = this.props;
     const { eventKey, pos } = node.props;
@@ -560,21 +617,30 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeClick = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
+  onNodeClick = (
+    e: React.MouseEvent<HTMLDivElement>,
+    treeNode: EventDataNode,
+  ) => {
     const { onClick } = this.props;
     if (onClick) {
       onClick(e, treeNode);
     }
   };
 
-  onNodeDoubleClick = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
+  onNodeDoubleClick = (
+    e: React.MouseEvent<HTMLDivElement>,
+    treeNode: EventDataNode,
+  ) => {
     const { onDoubleClick } = this.props;
     if (onDoubleClick) {
       onDoubleClick(e, treeNode);
     }
   };
 
-  onNodeSelect = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
+  onNodeSelect = (
+    e: React.MouseEvent<HTMLDivElement>,
+    treeNode: EventDataNode,
+  ) => {
     let { selectedKeys } = this.state;
     const { keyEntities } = this.state;
     const { onSelect, multiple } = this.props;
@@ -636,7 +702,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
     };
 
     if (checkStrictly) {
-      const checkedKeys = checked ? arrAdd(oriCheckedKeys, key) : arrDel(oriCheckedKeys, key);
+      const checkedKeys = checked
+        ? arrAdd(oriCheckedKeys, key)
+        : arrDel(oriCheckedKeys, key);
       const halfCheckedKeys = arrDel(oriHalfCheckedKeys, key);
       checkedObj = { checked: checkedKeys, halfChecked: halfCheckedKeys };
 
@@ -700,7 +768,11 @@ class Tree extends React.Component<TreeProps, TreeState> {
         const { loadData, onLoad } = this.props;
         const { key } = treeNode;
 
-        if (!loadData || loadedKeys.indexOf(key) !== -1 || loadingKeys.indexOf(key) !== -1) {
+        if (
+          !loadData ||
+          loadedKeys.indexOf(key) !== -1 ||
+          loadingKeys.indexOf(key) !== -1
+        ) {
           // react 15 will warn if return null
           return {};
         }
@@ -708,7 +780,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
         // Process load data
         const promise = loadData(treeNode);
         promise.then(() => {
-          const { loadedKeys: currentLoadedKeys, loadingKeys: currentLoadingKeys } = this.state;
+          const {
+            loadedKeys: currentLoadedKeys,
+            loadingKeys: currentLoadingKeys,
+          } = this.state;
           const newLoadedKeys = arrAdd(currentLoadedKeys, key);
           const newLoadingKeys = arrDel(currentLoadingKeys, key);
 
@@ -737,7 +812,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
       });
     });
 
-  onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
+  onNodeExpand = (
+    e: React.MouseEvent<HTMLDivElement>,
+    treeNode: EventDataNode,
+  ) => {
     let { expandedKeys } = this.state;
     const { treeData } = this.state;
     const { onExpand, loadData } = this.props;
@@ -775,7 +853,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
       return loadPromise
         ? loadPromise.then(() => {
             // [Legacy] Refresh logic
-            const newFlattenTreeData = flattenTreeData(this.state.treeData, expandedKeys);
+            const newFlattenTreeData = flattenTreeData(
+              this.state.treeData,
+              expandedKeys,
+            );
             this.setUncontrolledState({ flattenNodes: newFlattenTreeData });
           })
         : null;
@@ -784,21 +865,30 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return null;
   };
 
-  onNodeMouseEnter = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
+  onNodeMouseEnter = (
+    event: React.MouseEvent<HTMLDivElement>,
+    node: EventDataNode,
+  ) => {
     const { onMouseEnter } = this.props;
     if (onMouseEnter) {
       onMouseEnter({ event, node });
     }
   };
 
-  onNodeMouseLeave = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
+  onNodeMouseLeave = (
+    event: React.MouseEvent<HTMLDivElement>,
+    node: EventDataNode,
+  ) => {
     const { onMouseLeave } = this.props;
     if (onMouseLeave) {
       onMouseLeave({ event, node });
     }
   };
 
-  onNodeContextMenu = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
+  onNodeContextMenu = (
+    event: React.MouseEvent<HTMLDivElement>,
+    node: EventDataNode,
+  ) => {
     const { onRightClick } = this.props;
     if (onRightClick) {
       event.preventDefault();
@@ -876,7 +966,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
   offsetActiveKey = (offset: number) => {
     const { flattenNodes, activeKey } = this.state;
 
-    let index = flattenNodes.findIndex(({ data: { key } }) => key === activeKey);
+    let index = flattenNodes.findIndex(
+      ({ data: { key } }) => key === activeKey,
+    );
 
     // Align with index
     if (index === -1 && offset < 0) {
@@ -918,7 +1010,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
       const treeNodeRequiredProps = this.getTreeNodeRequiredProps();
 
       const expandable =
-        activeItem.data.isLeaf === false || !!(activeItem.data.children || []).length;
+        activeItem.data.isLeaf === false ||
+        !!(activeItem.data.children || []).length;
       const eventNode = convertNodePropsToEventData({
         ...getTreeNodeProps(activeKey, treeNodeRequiredProps),
         data: activeItem.data,
@@ -930,7 +1023,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
         case KeyCode.LEFT: {
           // Collapse if possible
           if (expandable && expandedKeys.includes(activeKey)) {
-            this.onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
+            this.onNodeExpand(
+              {} as React.MouseEvent<HTMLDivElement>,
+              eventNode,
+            );
           } else if (activeItem.parent) {
             this.onActiveChange(activeItem.parent.data.key);
           }
@@ -940,7 +1036,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
         case KeyCode.RIGHT: {
           // Expand if possible
           if (expandable && !expandedKeys.includes(activeKey)) {
-            this.onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
+            this.onNodeExpand(
+              {} as React.MouseEvent<HTMLDivElement>,
+              eventNode,
+            );
           } else if (activeItem.children && activeItem.children.length) {
             this.onActiveChange(activeItem.children[0].data.key);
           }
@@ -968,7 +1067,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
             !eventNode.disabled &&
             eventNode.selectable !== false
           ) {
-            this.onNodeSelect({} as React.MouseEvent<HTMLDivElement>, eventNode);
+            this.onNodeSelect(
+              {} as React.MouseEvent<HTMLDivElement>,
+              eventNode,
+            );
           }
           break;
         }
@@ -1004,7 +1106,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   render() {
-    const { focused, flattenNodes, keyEntities, dragging, activeKey } = this.state;
+    const {
+      focused,
+      flattenNodes,
+      keyEntities,
+      dragging,
+      activeKey,
+    } = this.state;
     const {
       prefixCls,
       className,
@@ -1026,7 +1134,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
       height,
       itemHeight,
     } = this.props;
-    const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
+    const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(
+      this.props,
+    );
 
     return (
       <TreeContext.Provider
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index e1cc4876..97caa6fe 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -4,6 +4,7 @@ import React from 'react';
 import { render, mount } from 'enzyme';
 import { renderToJson } from 'enzyme-to-json';
 import { resetWarned } from 'rc-util/lib/warning';
+import VirtualList from 'rc-virtual-list';
 import Tree, { TreeNode } from '../src';
 import { InternalTreeNode } from '../src/TreeNode';
 import { objectMatcher, spyConsole, spyError } from './util';
@@ -224,10 +225,14 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      const firstCheckboxClassNames = wrapper.find('.rc-tree-checkbox').instance().classList;
-      expect([].slice.call(firstCheckboxClassNames).includes(CHECKED_CLASSNAME.slice(1))).toBe(
-        false,
-      );
+      const firstCheckboxClassNames = wrapper
+        .find('.rc-tree-checkbox')
+        .instance().classList;
+      expect(
+        [].slice
+          .call(firstCheckboxClassNames)
+          .includes(CHECKED_CLASSNAME.slice(1)),
+      ).toBe(false);
     });
 
     it('controlled by checkedKeys', () => {
@@ -446,9 +451,21 @@ describe('Tree Basic', () => {
         return (
           <Tree checkable onCheck={mockHandleCheck}>
             <TreeNode title="parent 1" key="0-0">
-              <TreeNode title="leaf 1" key="0-0-1" disableCheckbox={disableCheckbox} />
-              <TreeNode title="leaf 2" key="0-0-2" disableCheckbox={disableCheckbox} />
-              <TreeNode title="leaf 3" key="0-0-3" disableCheckbox={disableCheckbox} />
+              <TreeNode
+                title="leaf 1"
+                key="0-0-1"
+                disableCheckbox={disableCheckbox}
+              />
+              <TreeNode
+                title="leaf 2"
+                key="0-0-2"
+                disableCheckbox={disableCheckbox}
+              />
+              <TreeNode
+                title="leaf 3"
+                key="0-0-3"
+                disableCheckbox={disableCheckbox}
+              />
             </TreeNode>
           </Tree>
         );
@@ -518,7 +535,9 @@ describe('Tree Basic', () => {
       it('works', () => {
         const checkedKeys = ['0-0-0'];
         const wrapper = mount(<Tree checkable checkedKeys={checkedKeys} />);
-        expect(errorSpy()).toHaveBeenCalledWith("Warning: Tree missing follow keys: '0-0-0'");
+        expect(errorSpy()).toHaveBeenCalledWith(
+          "Warning: Tree missing follow keys: '0-0-0'",
+        );
 
         wrapper.setProps({
           expandedKeys: ['0-0'],
@@ -632,7 +651,9 @@ describe('Tree Basic', () => {
 
         it('do not uncheck parent', () => {
           const wrapper = mount(<App />);
-          wrapper.setState({ checkedKeys: { checked: ['0-0', '0-0-0'], halfChecked: [] } });
+          wrapper.setState({
+            checkedKeys: { checked: ['0-0', '0-0-0'], halfChecked: [] },
+          });
           wrapper.find('.rc-tree-switcher').simulate('click');
           wrapper
             .find('.rc-tree-checkbox')
@@ -673,7 +694,9 @@ describe('Tree Basic', () => {
       firstNode().simulate('click');
 
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
+      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(
+        false,
+      );
       expect(
         wrapper
           .state()
@@ -683,7 +706,9 @@ describe('Tree Basic', () => {
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
+      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(
+        false,
+      );
       expect(wrapper.state().checkedKeys).toEqual([]);
     });
 
@@ -710,7 +735,9 @@ describe('Tree Basic', () => {
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
+      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(
+        false,
+      );
       expect(
         wrapper
           .state()
@@ -720,7 +747,9 @@ describe('Tree Basic', () => {
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
+      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(
+        false,
+      );
       expect(wrapper.state().checkedKeys).toEqual([]);
     });
 
@@ -753,11 +782,15 @@ describe('Tree Basic', () => {
 
       getNode().simulate('click');
       expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
+      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(
+        false,
+      );
       expect(getParent().hasClass('rc-tree-checkbox-checked')).toBe(true);
       getNode().simulate('click');
       expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
-      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
+      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(
+        false,
+      );
       expect(getParent().hasClass('rc-tree-checkbox-checked')).toBe(false);
     });
   });
@@ -771,7 +804,9 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(true);
+      expect(
+        wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME),
+      ).toBe(true);
     });
 
     it('controlled by selectedKeys', () => {
@@ -782,9 +817,13 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(false);
+      expect(
+        wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME),
+      ).toBe(false);
       wrapper.setProps({ selectedKeys: ['0-0'] });
-      expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(true);
+      expect(
+        wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME),
+      ).toBe(true);
     });
 
     it('fires select event', () => {
@@ -911,7 +950,9 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
+    const node = convertNodePropsToEventData(
+      wrapper.find(InternalTreeNode).props(),
+    );
     wrapper.find('.rc-tree-node-content-wrapper').simulate('contextMenu');
     expect(handleRightClick.mock.calls[0][0].node).toEqual(node);
   });
@@ -938,7 +979,9 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
+    const node = convertNodePropsToEventData(
+      wrapper.find(InternalTreeNode).props(),
+    );
     wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseEnter');
     expect(handleMouseEnter.mock.calls[0][0].node).toEqual(node);
   });
@@ -952,7 +995,9 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
+    const node = convertNodePropsToEventData(
+      wrapper.find(InternalTreeNode).props(),
+    );
     wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseLeave');
     expect(handleMouseLeave.mock.calls[0][0].node).toEqual(node);
   });
@@ -989,7 +1034,9 @@ describe('Tree Basic', () => {
         <TreeNode title="parent 1" key="0-0" />
       </Tree>,
     );
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
+    const node = convertNodePropsToEventData(
+      wrapper.find(InternalTreeNode).props(),
+    );
     wrapper.find('.rc-tree-switcher').simulate('click');
     expect(loadData).toHaveBeenCalledWith(node);
     expect(then).toHaveBeenCalled();
@@ -1010,7 +1057,9 @@ describe('Tree Basic', () => {
     it('fires dragStart event', () => {
       const onDragStart = jest.fn();
       const wrapper = mount(createTree({ onDragStart }));
-      const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
+      const treeNode = wrapper.find(
+        '.dragTarget > .rc-tree-node-content-wrapper',
+      );
       const node = convertNodePropsToEventData(
         wrapper
           .find(InternalTreeNode)
@@ -1026,7 +1075,9 @@ describe('Tree Basic', () => {
       const onDragEnter = jest.fn();
       const wrapper = mount(createTree({ onDragEnter }));
 
-      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+      wrapper
+        .find('.dragTarget > .rc-tree-node-content-wrapper')
+        .simulate('dragStart');
       wrapper
         .find('.dropTarget')
         .at(0)
@@ -1085,7 +1136,9 @@ describe('Tree Basic', () => {
     it('fires drop event', () => {
       const onDrop = jest.fn();
       const wrapper = mount(createTree({ onDrop }));
-      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+      wrapper
+        .find('.dragTarget > .rc-tree-node-content-wrapper')
+        .simulate('dragStart');
       const dropNode = convertNodePropsToEventData(
         wrapper
           .find(InternalTreeNode)
@@ -1117,7 +1170,9 @@ describe('Tree Basic', () => {
           .at(1)
           .props(),
       );
-      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
+      wrapper
+        .find('.dragTarget > .rc-tree-node-content-wrapper')
+        .simulate('dragEnd');
       const event = onDragEnd.mock.calls[0][0];
       expect(event.node).toEqual(node);
     });
@@ -1133,7 +1188,9 @@ describe('Tree Basic', () => {
       const dragTree = wrapper.find(Tree).at(0);
       const normalTree = wrapper.find(Tree).at(1);
 
-      dragTree.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+      dragTree
+        .find('.dragTarget > .rc-tree-node-content-wrapper')
+        .simulate('dragStart');
       normalTree
         .find('.dropTarget')
         .at(0)
@@ -1162,7 +1219,9 @@ describe('Tree Basic', () => {
             </Tree>,
           );
 
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+          wrapper
+            .find('.dragTarget > .rc-tree-node-content-wrapper')
+            .simulate('dragStart');
 
           // 1. Move into target (first in the middle of the node)
           wrapper.find(targetSelector).simulate('dragEnter', { clientY: 10 });
@@ -1175,7 +1234,9 @@ describe('Tree Basic', () => {
             // 3. Move in again
             wrapper.find(targetSelector).simulate('dragEnter', { clientY: 0 });
             setTimeout(() => {
-              wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
+              wrapper
+                .find(targetSelector)
+                .simulate('dragOver', { clientY: 999 });
 
               // 4. Drop
               wrapper.find(targetSelector).simulate('drop');
@@ -1292,7 +1353,9 @@ describe('Tree Basic', () => {
             setCheckedKeys(keys);
             onCheck(keys);
           }}
-          treeData={[{ key: 11, title: 11, children: [{ key: 22, title: 22 }] }]}
+          treeData={[
+            { key: 11, title: 11, children: [{ key: 22, title: 22 }] },
+          ]}
         />
       );
     };
@@ -1331,4 +1394,24 @@ describe('Tree Basic', () => {
       </Tree>,
     );
   });
+
+  it('not crash if expandedKeys change to undefined', () => {
+    const wrapper = mount(
+      <Tree expandedKeys={['light']}>
+        <TreeNode key="light" title="light">
+          <TreeNode key="bamboo" title="bamboo" />
+        </TreeNode>
+      </Tree>,
+    );
+    const { data } = wrapper.find(VirtualList).props();
+    expect(data).toHaveLength(2);
+
+    wrapper.setProps({ expandedKeys: undefined });
+
+    // Click should not crash
+    wrapper
+      .find('.rc-tree-switcher')
+      .first()
+      .simulate('click');
+  });
 });

From fcf7024e7f51b3e30de92b295c2bc0d16fe9402d Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 24 Dec 2019 15:43:32 +0800
Subject: [PATCH 052/397] chore: update ts define

---
 src/utils/treeUtil.ts | 26 +++++++++++++++++++++-----
 1 file changed, 21 insertions(+), 5 deletions(-)

diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 274f3cca..aeb45a11 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -1,7 +1,14 @@
 import * as React from 'react';
 import toArray from 'rc-util/lib/Children/toArray';
 import warning from 'rc-util/lib/warning';
-import { DataNode, FlattenNode, NodeElement, DataEntity, Key, EventDataNode } from '../interface';
+import {
+  DataNode,
+  FlattenNode,
+  NodeElement,
+  DataEntity,
+  Key,
+  EventDataNode,
+} from '../interface';
 import { getPosition, isTreeNode } from '../util';
 import { TreeNodeProps } from '../TreeNode';
 
@@ -45,12 +52,15 @@ export function warningWithoutKey(treeData: DataNode[] = []) {
  */
 export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
   function dig(node: React.ReactNode): DataNode[] {
-    const treeNodes: NodeElement[] = toArray(node);
+    const treeNodes = toArray(node) as NodeElement[];
     return treeNodes
       .map(treeNode => {
         // Filter invalidate node
         if (!isTreeNode(treeNode)) {
-          warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
+          warning(
+            !treeNode,
+            'Tree/TreeNode can only accept TreeNode as children.',
+          );
           return null;
         }
 
@@ -161,7 +171,11 @@ export function traverseDataNodes(
     // Process children node
     if (children) {
       children.forEach((subNode, subIndex) => {
-        processNode(subNode, subIndex, { node, pos, level: parent ? parent.level + 1 : -1 });
+        processNode(subNode, subIndex, {
+          node,
+          pos,
+          level: parent ? parent.level + 1 : -1,
+        });
       });
     }
   }
@@ -278,7 +292,9 @@ export function getTreeNodeProps(
   return treeNodeProps;
 }
 
-export function convertNodePropsToEventData(props: TreeNodeProps): EventDataNode {
+export function convertNodePropsToEventData(
+  props: TreeNodeProps,
+): EventDataNode {
   const {
     data,
     expanded,

From bc3aa35a7d3ca50a5a4a49936afe5c7aceac70df Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 24 Dec 2019 15:44:07 +0800
Subject: [PATCH 053/397] v3.0.0-alpha.38

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1a645e55..3e81088c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.37",
+  "version": "3.0.0-alpha.38",
   "description": "tree ui component for react",
   "keywords": [
     "react",

From 6c6e1fd0c6f2656572aefb5fbc80085abf2f19b8 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 31 Dec 2019 15:07:00 +0800
Subject: [PATCH 054/397] chore: Add todo comment

---
 examples/animation.jsx | 1 +
 src/Tree.tsx           | 1 +
 2 files changed, 2 insertions(+)

diff --git a/examples/animation.jsx b/examples/animation.jsx
index 3b406208..288324d3 100644
--- a/examples/animation.jsx
+++ b/examples/animation.jsx
@@ -27,6 +27,7 @@ const motion = {
 };
 
 function getTreeData() {
+  // big-data: generateData(1000, 3, 2)
   return [
     {
       key: '0',
diff --git a/src/Tree.tsx b/src/Tree.tsx
index c01cdb16..d10c2f82 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -836,6 +836,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       expandedKeys = arrDel(expandedKeys, key);
     }
 
+    // TODO: optimize big data flatten logic
     const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys);
     this.setUncontrolledState({ expandedKeys, flattenNodes });
 

From 6c7ebca9a174fba16973b747c2ca154613b9e2b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Thu, 9 Jan 2020 14:58:36 +0800
Subject: [PATCH 055/397] fix: Tree State should keep sync instead of part
 update (#289)

* fix: Table expanded logic

* add test case

* patch engines
---
 package.json       |  3 +++
 src/Tree.tsx       | 17 ++++++++++++-----
 tests/Tree.spec.js | 15 +++++++++++++++
 3 files changed, 30 insertions(+), 5 deletions(-)

diff --git a/package.json b/package.json
index 3e81088c..df16750e 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,9 @@
   "name": "rc-tree",
   "version": "3.0.0-alpha.38",
   "description": "tree ui component for react",
+  "engines": {
+    "node": ">=8.x"
+  },
   "keywords": [
     "react",
     "react-component",
diff --git a/src/Tree.tsx b/src/Tree.tsx
index d10c2f82..420e2a8f 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -75,7 +75,7 @@ export interface TreeProps {
   defaultExpandedKeys?: Key[];
   expandedKeys?: Key[];
   defaultCheckedKeys?: Key[];
-  checkedKeys?: (Key)[] | { checked: (Key)[]; halfChecked: Key[] };
+  checkedKeys?: Key[] | { checked: Key[]; halfChecked: Key[] };
   defaultSelectedKeys?: Key[];
   selectedKeys?: Key[];
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
@@ -838,7 +838,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
     // TODO: optimize big data flatten logic
     const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys);
-    this.setUncontrolledState({ expandedKeys, flattenNodes });
+    this.setUncontrolledState({ expandedKeys, flattenNodes }, true);
 
     if (onExpand) {
       onExpand(expandedKeys, {
@@ -1086,18 +1086,25 @@ class Tree extends React.Component<TreeProps, TreeState> {
   /**
    * Only update the value which is not in props
    */
-  setUncontrolledState = state => {
+  setUncontrolledState = (
+    state: Partial<TreeState>,
+    atomic: boolean = false,
+  ) => {
     let needSync = false;
+    let allPassed = true;
     const newState = {};
 
     Object.keys(state).forEach(name => {
-      if (name in this.props) return;
+      if (name in this.props) {
+        allPassed = false;
+        return;
+      }
 
       needSync = true;
       newState[name] = state[name];
     });
 
-    if (needSync) {
+    if (needSync && (!atomic || allPassed)) {
       this.setState(newState);
     }
   };
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 97caa6fe..284dc3c9 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1414,4 +1414,19 @@ describe('Tree Basic', () => {
       .first()
       .simulate('click');
   });
+
+  it('controlled should block expanded', () => {
+    const wrapper = mount(
+      <Tree
+        expandedKeys={['1']}
+        treeData={[{ key: '1', title: 1, children: [{ key: '2', title: 2 }] }]}
+      />,
+    );
+
+    wrapper
+      .find('.rc-tree-switcher')
+      .first()
+      .simulate('click');
+    expect(wrapper.find('List').props().data).toHaveLength(2);
+  });
 });

From fc8a3d2ca76522b57982159f6acd93d845f32ab2 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 9 Jan 2020 14:59:49 +0800
Subject: [PATCH 056/397] v3.0.0-rc.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index df16750e..301bc311 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-alpha.38",
+  "version": "3.0.0-rc.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From c72e94e505aec026ff28fc46b896fbbd19d90a53 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 9 Jan 2020 15:16:39 +0800
Subject: [PATCH 057/397] refator: Improve perf

---
 src/Tree.tsx          | 1 -
 src/utils/treeUtil.ts | 3 ++-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 420e2a8f..4aa392ac 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -836,7 +836,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
       expandedKeys = arrDel(expandedKeys, key);
     }
 
-    // TODO: optimize big data flatten logic
     const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys);
     this.setUncontrolledState({ expandedKeys, flattenNodes }, true);
 
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index aeb45a11..43019844 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -95,6 +95,7 @@ export function flattenTreeData(
   treeNodeList: DataNode[] = [],
   expandedKeys: Key[] | true = [],
 ): FlattenNode[] {
+  const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys);
   const flattenList: FlattenNode[] = [];
 
   function dig(list: DataNode[], parent: FlattenNode = null): FlattenNode[] {
@@ -116,7 +117,7 @@ export function flattenTreeData(
       flattenList.push(flattenNode);
 
       // Loop treeNode children
-      if (expandedKeys === true || expandedKeys.includes(mergedKey)) {
+      if (expandedKeys === true || expandedKeySet.has(mergedKey)) {
         flattenNode.children = dig(treeNode.children || [], flattenNode);
       } else {
         flattenNode.children = [];

From e8d600d85c58e455e03fa91c8be5094f0f67c9e9 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 9 Jan 2020 15:18:15 +0800
Subject: [PATCH 058/397] v3.0.0-rc.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 301bc311..c906ed7b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-rc.0",
+  "version": "3.0.0-rc.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From e188e340753e37e3c6cdff902945b31e4869d58b Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Fri, 7 Feb 2020 21:31:44 +0000
Subject: [PATCH 059/397] chore(deps-dev): bump np from 5.2.1 to 6.0.0

Bumps [np](https://github.com/sindresorhus/np) from 5.2.1 to 6.0.0.
- [Release notes](https://github.com/sindresorhus/np/releases)
- [Commits](https://github.com/sindresorhus/np/compare/v5.2.1...v6.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c906ed7b..036f03c9 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,7 @@
     "enzyme-adapter-react-16": "^1.1.1",
     "enzyme-to-json": "^3.0.0",
     "father": "^2.25.0-alpha.1",
-    "np": "^5.0.3",
+    "np": "^6.0.0",
     "rc-dialog": "^7.3.1",
     "rc-tooltip": "3.x",
     "rc-trigger": "^2.0.0",

From c26499e0e4b54cae8b810c5713f35ef9232a270f Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 26 Feb 2020 22:51:14 +0800
Subject: [PATCH 060/397] v3.0.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 036f03c9..4034b232 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0-rc.1",
+  "version": "3.0.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From f37dc543dc7552b4c5454569c9c9f55e8d1326ac Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Wed, 26 Feb 2020 21:33:35 +0000
Subject: [PATCH 061/397] chore(deps-dev): bump rc-trigger from 2.6.5 to 4.0.0

Bumps [rc-trigger](https://github.com/react-component/trigger) from 2.6.5 to 4.0.0.
- [Release notes](https://github.com/react-component/trigger/releases)
- [Changelog](https://github.com/react-component/trigger/blob/master/HISTORY.md)
- [Commits](https://github.com/react-component/trigger/compare/2.6.5...v4.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4034b232..b4fd0c0a 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
     "np": "^6.0.0",
     "rc-dialog": "^7.3.1",
     "rc-tooltip": "3.x",
-    "rc-trigger": "^2.0.0",
+    "rc-trigger": "^4.0.0",
     "react": "^16.8.0",
     "react-dom": "^16.8.0",
     "typescript": "^3.5.2"

From 0dfb6cbe1a509116dce569836638e27a497b23d3 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 27 Feb 2020 12:41:21 +0800
Subject: [PATCH 062/397] chore: update deps

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4034b232..edda826a 100644
--- a/package.json
+++ b/package.json
@@ -67,7 +67,7 @@
     "prop-types": "^15.5.8",
     "rc-animate": "^2.9.2",
     "rc-util": "^4.11.0",
-    "rc-virtual-list": "^0.0.0-alpha.28",
+    "rc-virtual-list": "^1.0.0",
     "react-lifecycles-compat": "^3.0.4"
   }
 }

From 8fdbf7cb4de3d64e47ad16c41879e95da2515894 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 27 Feb 2020 12:42:46 +0800
Subject: [PATCH 063/397] v3.0.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index edda826a..8181e7c6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.0",
+  "version": "3.0.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 8b27b8346e07f2647a0bbf044680d37fe3640aac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Thu, 5 Mar 2020 17:07:24 +0800
Subject: [PATCH 064/397] Make `active` TreeNode prop optional (#298)

When I try to use the TypeScript types, it wants me to provide an `active` prop for `TreeNode`. Based on the documentation and the fact that the prop is under the "By parent" section, the user should not have to supply the `active` prop.
---
 src/TreeNode.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 4903fbfa..7c221c63 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -37,7 +37,7 @@ export interface TreeNodeProps {
   data?: DataNode;
   isStart?: boolean[];
   isEnd?: boolean[];
-  active: boolean;
+  active?: boolean;
   onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
 
   // By user

From 9e973e5f53e417b2b75fbe147fef675f37f7c60b Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 5 Mar 2020 17:08:17 +0800
Subject: [PATCH 065/397] v3.0.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1cd63e21..9454b9d4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.1",
+  "version": "3.0.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From bf9751d8550e90276d56e39af8679fc5529be672 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sat, 7 Mar 2020 00:21:53 +0800
Subject: [PATCH 066/397] feat: Support `virtual` prop ref
 https://github.com/react-component/select/pull/460

---
 README.md          |  1 +
 package.json       |  2 +-
 src/NodeList.tsx   | 25 ++++++++++++++++++++-----
 src/Tree.tsx       |  3 +++
 tests/Tree.spec.js | 16 ++++++++++++++++
 5 files changed, 41 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index a4df0172..48adfccd 100644
--- a/README.md
+++ b/README.md
@@ -85,6 +85,7 @@ see examples
 | onRightClick | select current treeNode and show customized contextmenu | function({event,node}) | - |
 | onSelect | click the treeNode to fire | function(selectedKeys, e:{selected: bool, selectedNodes, node, event, nativeEvent}) | - |
 | switcherIcon | specific the switcher icon. | ReactNode / (props: TreeNodeAttribute) => ReactNode | - |
+| virtual | Disable virtual scroll when `false` | boolean | - |
 
 ### TreeNode props
 
diff --git a/package.json b/package.json
index 9454b9d4..665d2b69 100644
--- a/package.json
+++ b/package.json
@@ -67,7 +67,7 @@
     "prop-types": "^15.5.8",
     "rc-animate": "^2.9.2",
     "rc-util": "^4.11.0",
-    "rc-virtual-list": "^1.0.0",
+    "rc-virtual-list": "^1.1.0",
     "react-lifecycles-compat": "^3.0.4"
   }
 }
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 428c43fa..dcbc98db 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -78,6 +78,7 @@ interface NodeListProps {
   // Virtual list
   height: number;
   itemHeight: number;
+  virtual?: boolean;
 
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
@@ -120,7 +121,10 @@ function getAccessibilityPath(item: FlattenNode): string {
   return path;
 }
 
-const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (props, ref) => {
+const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
+  props,
+  ref,
+) => {
   const {
     prefixCls,
     data,
@@ -142,6 +146,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
 
     height,
     itemHeight,
+    virtual,
 
     focusable,
     activeItem,
@@ -170,7 +175,9 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
   const [prevData, setPrevData] = React.useState(data);
   const [transitionData, setTransitionData] = React.useState(data);
   const [transitionRange, setTransitionRange] = React.useState([]);
-  const [motionType, setMotionType] = React.useState<'show' | 'hide' | null>(null);
+  const [motionType, setMotionType] = React.useState<'show' | 'hide' | null>(
+    null,
+  );
 
   function onMotionEnd() {
     setPrevData(data);
@@ -188,7 +195,9 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
 
     if (diffExpanded.key !== null) {
       if (diffExpanded.add) {
-        const keyIndex = prevData.findIndex(({ data: { key } }) => key === diffExpanded.key);
+        const keyIndex = prevData.findIndex(
+          ({ data: { key } }) => key === diffExpanded.key,
+        );
 
         if (motion) setDisableVirtual(true);
         const rangeNodes = getMinimumRangeTransitionRange(
@@ -204,7 +213,9 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         setTransitionRange(rangeNodes);
         setMotionType('show');
       } else {
-        const keyIndex = data.findIndex(({ data: { key } }) => key === diffExpanded.key);
+        const keyIndex = data.findIndex(
+          ({ data: { key } }) => key === diffExpanded.key,
+        );
 
         if (motion) setDisableVirtual(true);
         const rangeNodes = getMinimumRangeTransitionRange(
@@ -276,6 +287,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         itemKey={itemKey}
         height={height}
         fullHeight={false}
+        virtual={virtual}
         itemHeight={itemHeight}
         onSkipRender={onMotionEnd}
         prefixCls={`${prefixCls}-list`}
@@ -291,7 +303,10 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
           const mergedKey = getKey(key, pos);
           delete restProps.children;
 
-          const treeNodeProps = getTreeNodeProps(mergedKey, treeNodeRequiredProps);
+          const treeNodeProps = getTreeNodeProps(
+            mergedKey,
+            treeNodeRequiredProps,
+          );
 
           return (
             <MotionTreeNode
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 4aa392ac..0318b115 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -161,6 +161,7 @@ export interface TreeProps {
   // Virtual List
   height?: number;
   itemHeight?: number;
+  virtual?: boolean;
 }
 
 interface TreeState {
@@ -1140,6 +1141,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       filterTreeNode,
       height,
       itemHeight,
+      virtual,
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(
       this.props,
@@ -1198,6 +1200,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
             dragging={dragging}
             height={height}
             itemHeight={itemHeight}
+            virtual={virtual}
             focusable={focusable}
             focused={focused}
             tabIndex={tabIndex}
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 284dc3c9..472ee7e8 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1429,4 +1429,20 @@ describe('Tree Basic', () => {
       .simulate('click');
     expect(wrapper.find('List').props().data).toHaveLength(2);
   });
+
+  it('support virtual', () => {
+    const data = [];
+    for (let i = 0; i < 99; i += 1) {
+      data.push({
+        key: i,
+        title: i,
+      });
+    }
+
+    const wrapper = mount(
+      <Tree itemHeight={10} height={100} treeData={data} virtual={false} />,
+    );
+
+    expect(wrapper.find('List').props().virtual).toBe(false);
+  });
 });

From f88c305d8bd9fbd072bcc9886c2079f4fb4ce4cb Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sat, 7 Mar 2020 00:29:20 +0800
Subject: [PATCH 067/397] chore: Fix TS define

---
 src/MotionTreeNode.tsx | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 484eb037..255eecf1 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -17,7 +17,10 @@ interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
   treeNodeRequiredProps: TreeNodeRequiredProps;
 }
 
-const MotionTreeNode: React.FC<MotionTreeNodeProps> = (
+const MotionTreeNode: React.ForwardRefRenderFunction<
+  CSSMotion,
+  MotionTreeNodeProps
+> = (
   {
     className,
     style,
@@ -53,7 +56,10 @@ const MotionTreeNode: React.FC<MotionTreeNodeProps> = (
         {({ className: motionClassName, style: motionStyle }, motionRef) => (
           <div
             ref={motionRef}
-            className={classNames(`${prefixCls}-treenode-motion`, motionClassName)}
+            className={classNames(
+              `${prefixCls}-treenode-motion`,
+              motionClassName,
+            )}
             style={motionStyle}
           >
             {motionNodes.map((treeNode: FlattenNode) => {
@@ -64,7 +70,10 @@ const MotionTreeNode: React.FC<MotionTreeNodeProps> = (
               } = treeNode;
               delete restProps.children;
 
-              const treeNodeProps = getTreeNodeProps(key, treeNodeRequiredProps);
+              const treeNodeProps = getTreeNodeProps(
+                key,
+                treeNodeRequiredProps,
+              );
 
               return (
                 <TreeNode
@@ -83,7 +92,15 @@ const MotionTreeNode: React.FC<MotionTreeNodeProps> = (
       </CSSMotion>
     );
   }
-  return <TreeNode domRef={ref} className={className} style={style} {...props} active={active} />;
+  return (
+    <TreeNode
+      domRef={ref}
+      className={className}
+      style={style}
+      {...props}
+      active={active}
+    />
+  );
 };
 
 MotionTreeNode.displayName = 'MotionTreeNode';

From 0f6e7000ca503afa5405ea85aa17b0413c8fbbde Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sat, 7 Mar 2020 00:31:23 +0800
Subject: [PATCH 068/397] v3.1.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 665d2b69..dc10b67e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.0.2",
+  "version": "3.1.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From d34559f22df0a5a257ec1dabca2a76347024c82c Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 11 Mar 2020 10:31:19 +0800
Subject: [PATCH 069/397] chore: Update script

---
 package.json | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index dc10b67e..9def7caf 100644
--- a/package.json
+++ b/package.json
@@ -35,10 +35,12 @@
   "scripts": {
     "start": "father doc dev --storybook",
     "build": "father doc build --storybook",
-    "compile": "father build",
+    "compile": "father build && lessc assets/index.less assets/index.css",
     "prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish",
+    "postpublish": "npm run gh-pages",
     "lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js",
     "test": "father test",
+    "gh-pages": "npm run build && father doc deploy",
     "now-build": "npm run build"
   },
   "peerDependencies": {
@@ -54,6 +56,7 @@
     "enzyme-adapter-react-16": "^1.1.1",
     "enzyme-to-json": "^3.0.0",
     "father": "^2.25.0-alpha.1",
+    "less": "^3.11.1",
     "np": "^6.0.0",
     "rc-dialog": "^7.3.1",
     "rc-tooltip": "3.x",

From 42df2b0ddfc80f198a3dbfecdbeadc9afbd75a2a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 11 Mar 2020 10:34:14 +0800
Subject: [PATCH 070/397] docs: Update readme

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 48adfccd..c161583c 100644
--- a/README.md
+++ b/README.md
@@ -29,9 +29,9 @@ tree ui component for react
 
 ## Example
 
-http://localhost:8018/examples/
+http://localhost:9001/
 
-online example: http://react-component.github.io/tree/examples/
+online example: http://react-component.github.io/tree/
 
 ## install
 

From 8db3ded5bd8e05529c3e4fd941a181d298393053 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 11 Mar 2020 10:36:50 +0800
Subject: [PATCH 071/397] v3.1.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 9def7caf..85afaf56 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.0",
+  "version": "3.1.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From e919c78fea85233d7a9b5d7865bd4f7092959a38 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Thu, 12 Mar 2020 11:32:29 +0800
Subject: [PATCH 072/397] chore(deps-dev): bump rc-tooltip from 3.7.3 to 4.0.2
 (#300)

Bumps [rc-tooltip](https://github.com/react-component/tooltip) from 3.7.3 to 4.0.2.
- [Release notes](https://github.com/react-component/tooltip/releases)
- [Changelog](https://github.com/react-component/tooltip/blob/master/HISTORY.md)
- [Commits](https://github.com/react-component/tooltip/compare/3.7.3...v4.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 85afaf56..31d796e6 100644
--- a/package.json
+++ b/package.json
@@ -59,7 +59,7 @@
     "less": "^3.11.1",
     "np": "^6.0.0",
     "rc-dialog": "^7.3.1",
-    "rc-tooltip": "3.x",
+    "rc-tooltip": "4.x",
     "rc-trigger": "^4.0.0",
     "react": "^16.8.0",
     "react-dom": "^16.8.0",

From 772872b39f4107a06f59be470e10ffcec91993bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Wed, 18 Mar 2020 23:19:03 +0800
Subject: [PATCH 073/397] fix: Tree conduct logic (#302)

* fix conduct

* add test case
---
 .eslintrc.js             |  3 ++-
 .prettierrc              |  3 ++-
 src/utils/conductUtil.ts |  3 ++-
 tests/util.spec.js       | 22 ++++++++++++++++++++++
 4 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/.eslintrc.js b/.eslintrc.js
index 902cd574..de71e3df 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -11,5 +11,6 @@ module.exports = {
     'react/no-unused-prop-types': 1,
     'jsx-a11y/label-has-for': 0,
     'jsx-a11y/label-has-associated-control': 0,
+    'no-loop-func': 0,
   },
-};
\ No newline at end of file
+};
diff --git a/.prettierrc b/.prettierrc
index 27dd8afb..f307fb19 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -3,5 +3,6 @@
   "semi": true,
   "singleQuote": true,
   "tabWidth": 2,
-  "trailingComma": "all"
+  "trailingComma": "all",
+  "printWidth": 100
 }
diff --git a/src/utils/conductUtil.ts b/src/utils/conductUtil.ts
index 94026d86..766357bd 100644
--- a/src/utils/conductUtil.ts
+++ b/src/utils/conductUtil.ts
@@ -104,7 +104,7 @@ function cleanConductCheck(
   maxLevel: number,
 ): ConductReturnType {
   const checkedKeys = new Set<Key>(keys);
-  const halfCheckedKeys = new Set<Key>(halfKeys);
+  let halfCheckedKeys = new Set<Key>(halfKeys);
 
   // Remove checked keys from top to bottom
   for (let level = 0; level <= maxLevel; level += 1) {
@@ -123,6 +123,7 @@ function cleanConductCheck(
   }
 
   // Remove checked keys form bottom to top
+  halfCheckedKeys = new Set<Key>();
   const visitedKeys = new Set<Key>();
   for (let level = maxLevel; level >= 0; level -= 1) {
     const entities = levelEntities.get(level) || new Set();
diff --git a/tests/util.spec.js b/tests/util.spec.js
index ed9f3edb..040b7267 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -194,6 +194,28 @@ describe('Util', () => {
         expect(halfCheckedKeys.sort()).toEqual(['good', 'is'].sort());
       });
 
+      it('uncheck partial', () => {
+        const tree = (
+          <Tree>
+            <TreeNode key="0-0" title="0-0">
+              <TreeNode key="0-0-0" title="0-0-0" />
+              <TreeNode key="0-0-1" title="0-0-1" />
+            </TreeNode>
+          </Tree>
+        );
+        const { keyEntities } = convertDataToEntities(convertTreeToData(tree.props.children));
+        const { checkedKeys, halfCheckedKeys } = conductCheck(
+          [],
+          {
+            checked: false,
+            halfCheckedKeys: ['0-0', '0-0-0'],
+          },
+          keyEntities,
+        );
+        expect(checkedKeys).toEqual([]);
+        expect(halfCheckedKeys).toEqual([]);
+      });
+
       describe('not exist', () => {
         const errorSpy = spyError();
 

From 35713884e5aa39ecdc2bc4107226da2b7ebf3afb Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 18 Mar 2020 23:20:32 +0800
Subject: [PATCH 074/397] v3.1.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 31d796e6..e86e3571 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.1",
+  "version": "3.1.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 98f12c27dd611daa1e21f75c38e77a1185659c2c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Thu, 2 Apr 2020 16:18:37 +0800
Subject: [PATCH 075/397] fix: Active trigger logic (#306)

* not repate trigger active event

* add test case
---
 examples/basic.jsx          |   1 +
 src/Tree.tsx                | 198 +++++++++---------------------------
 tests/Accessibility.spec.js |  15 +++
 3 files changed, 63 insertions(+), 151 deletions(-)

diff --git a/examples/basic.jsx b/examples/basic.jsx
index b24598fd..5e58d200 100644
--- a/examples/basic.jsx
+++ b/examples/basic.jsx
@@ -118,6 +118,7 @@ class Demo extends React.Component {
           defaultCheckedKeys={this.state.defaultCheckedKeys}
           onSelect={this.onSelect}
           onCheck={this.onCheck}
+          onActiveChange={key => console.log('Active:', key)}
         >
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title={customLabel} key="0-0-0">
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 0318b115..4cf25aa8 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -91,10 +91,7 @@ export interface TreeProps {
       nativeEvent: MouseEvent;
     },
   ) => void;
-  onCheck?: (
-    checked: { checked: Key[]; halfChecked: Key[] } | Key[],
-    info: CheckInfo,
-  ) => void;
+  onCheck?: (checked: { checked: Key[]; halfChecked: Key[] } | Key[], info: CheckInfo) => void;
   onSelect?: (
     selectedKeys: Key[],
     info: {
@@ -114,32 +111,17 @@ export interface TreeProps {
   ) => void;
   loadData?: (treeNode: EventDataNode) => Promise<void>;
   loadedKeys?: Key[];
-  onMouseEnter?: (info: {
-    event: React.MouseEvent;
-    node: EventDataNode;
-  }) => void;
-  onMouseLeave?: (info: {
-    event: React.MouseEvent;
-    node: EventDataNode;
-  }) => void;
-  onRightClick?: (info: {
-    event: React.MouseEvent;
-    node: EventDataNode;
-  }) => void;
-  onDragStart?: (info: {
-    event: React.MouseEvent;
-    node: EventDataNode;
-  }) => void;
+  onMouseEnter?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onMouseLeave?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onRightClick?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onDragStart?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
   onDragEnter?: (info: {
     event: React.MouseEvent;
     node: EventDataNode;
     expandedKeys: Key[];
   }) => void;
   onDragOver?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onDragLeave?: (info: {
-    event: React.MouseEvent;
-    node: EventDataNode;
-  }) => void;
+  onDragLeave?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
   onDragEnd?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
   onDrop?: (info: {
     event: React.MouseEvent;
@@ -213,10 +195,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     defaultExpandedKeys: PropTypes.arrayOf(keyPropType),
     expandedKeys: PropTypes.arrayOf(keyPropType),
     defaultCheckedKeys: PropTypes.arrayOf(keyPropType),
-    checkedKeys: PropTypes.oneOfType([
-      PropTypes.arrayOf(keyPropType),
-      PropTypes.object,
-    ]),
+    checkedKeys: PropTypes.oneOfType([PropTypes.arrayOf(keyPropType), PropTypes.object]),
     defaultSelectedKeys: PropTypes.arrayOf(keyPropType),
     selectedKeys: PropTypes.arrayOf(keyPropType),
     onClick: PropTypes.func,
@@ -298,10 +277,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     };
 
     function needSync(name: string) {
-      return (
-        (!prevProps && name in props) ||
-        (prevProps && prevProps[name] !== props[name])
-      );
+      return (!prevProps && name in props) || (prevProps && prevProps[name] !== props[name]);
     }
 
     // ================== Tree Node ==================
@@ -311,10 +287,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     if (needSync('treeData')) {
       ({ treeData } = props);
     } else if (needSync('children')) {
-      warning(
-        false,
-        '`children` of Tree is deprecated. Please use `treeData` instead.',
-      );
+      warning(false, '`children` of Tree is deprecated. Please use `treeData` instead.');
       treeData = convertTreeToData(props.children);
     }
 
@@ -336,10 +309,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     const keyEntities = newState.keyEntities || prevState.keyEntities;
 
     // ================ expandedKeys =================
-    if (
-      needSync('expandedKeys') ||
-      (prevProps && needSync('autoExpandParent'))
-    ) {
+    if (needSync('expandedKeys') || (prevProps && needSync('autoExpandParent'))) {
       newState.expandedKeys =
         props.autoExpandParent || (!prevProps && props.defaultExpandParent)
           ? conductExpandParent(props.expandedKeys, keyEntities)
@@ -347,9 +317,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     } else if (!prevProps && props.defaultExpandAll) {
       const cloneKeyEntities = { ...keyEntities };
       delete cloneKeyEntities[MOTION_KEY];
-      newState.expandedKeys = Object.keys(cloneKeyEntities).map(
-        key => cloneKeyEntities[key].key,
-      );
+      newState.expandedKeys = Object.keys(cloneKeyEntities).map(key => cloneKeyEntities[key].key);
     } else if (!prevProps && props.defaultExpandedKeys) {
       newState.expandedKeys =
         props.autoExpandParent || props.defaultExpandParent
@@ -375,10 +343,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       if (needSync('selectedKeys')) {
         newState.selectedKeys = calcSelectedKeys(props.selectedKeys, props);
       } else if (!prevProps && props.defaultSelectedKeys) {
-        newState.selectedKeys = calcSelectedKeys(
-          props.defaultSelectedKeys,
-          props,
-        );
+        newState.selectedKeys = calcSelectedKeys(props.defaultSelectedKeys, props);
       }
     }
 
@@ -419,10 +384,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return newState;
   }
 
-  onNodeDragStart = (
-    event: React.MouseEvent<HTMLDivElement>,
-    node: NodeInstance,
-  ) => {
+  onNodeDragStart = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
     const { eventKey } = node.props;
@@ -447,10 +409,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
    * Better for use mouse move event to refresh drag state.
    * But let's just keep it to avoid event trigger logic change.
    */
-  onNodeDragEnter = (
-    event: React.MouseEvent<HTMLDivElement>,
-    node: NodeInstance,
-  ) => {
+  onNodeDragEnter = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragEnter } = this.props;
     const { pos, eventKey } = node.props;
@@ -514,10 +473,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }, 0);
   };
 
-  onNodeDragOver = (
-    event: React.MouseEvent<HTMLDivElement>,
-    node: NodeInstance,
-  ) => {
+  onNodeDragOver = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { onDragOver } = this.props;
     const { eventKey } = node.props;
 
@@ -537,10 +493,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeDragLeave = (
-    event: React.MouseEvent<HTMLDivElement>,
-    node: NodeInstance,
-  ) => {
+  onNodeDragLeave = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { onDragLeave } = this.props;
 
     this.setState({
@@ -552,10 +505,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeDragEnd = (
-    event: React.MouseEvent<HTMLDivElement>,
-    node: NodeInstance,
-  ) => {
+  onNodeDragEnd = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { onDragEnd } = this.props;
     this.setState({
       dragOverNodeKey: '',
@@ -569,10 +519,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     this.dragNode = null;
   };
 
-  onNodeDrop = (
-    event: React.MouseEvent<HTMLDivElement>,
-    node: NodeInstance,
-  ) => {
+  onNodeDrop = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
     const { dragNodesKeys = [], dropPosition } = this.state;
     const { onDrop } = this.props;
     const { eventKey, pos } = node.props;
@@ -618,30 +565,21 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeClick = (
-    e: React.MouseEvent<HTMLDivElement>,
-    treeNode: EventDataNode,
-  ) => {
+  onNodeClick = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     const { onClick } = this.props;
     if (onClick) {
       onClick(e, treeNode);
     }
   };
 
-  onNodeDoubleClick = (
-    e: React.MouseEvent<HTMLDivElement>,
-    treeNode: EventDataNode,
-  ) => {
+  onNodeDoubleClick = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     const { onDoubleClick } = this.props;
     if (onDoubleClick) {
       onDoubleClick(e, treeNode);
     }
   };
 
-  onNodeSelect = (
-    e: React.MouseEvent<HTMLDivElement>,
-    treeNode: EventDataNode,
-  ) => {
+  onNodeSelect = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     let { selectedKeys } = this.state;
     const { keyEntities } = this.state;
     const { onSelect, multiple } = this.props;
@@ -703,9 +641,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     };
 
     if (checkStrictly) {
-      const checkedKeys = checked
-        ? arrAdd(oriCheckedKeys, key)
-        : arrDel(oriCheckedKeys, key);
+      const checkedKeys = checked ? arrAdd(oriCheckedKeys, key) : arrDel(oriCheckedKeys, key);
       const halfCheckedKeys = arrDel(oriHalfCheckedKeys, key);
       checkedObj = { checked: checkedKeys, halfChecked: halfCheckedKeys };
 
@@ -769,11 +705,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         const { loadData, onLoad } = this.props;
         const { key } = treeNode;
 
-        if (
-          !loadData ||
-          loadedKeys.indexOf(key) !== -1 ||
-          loadingKeys.indexOf(key) !== -1
-        ) {
+        if (!loadData || loadedKeys.indexOf(key) !== -1 || loadingKeys.indexOf(key) !== -1) {
           // react 15 will warn if return null
           return {};
         }
@@ -781,10 +713,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         // Process load data
         const promise = loadData(treeNode);
         promise.then(() => {
-          const {
-            loadedKeys: currentLoadedKeys,
-            loadingKeys: currentLoadingKeys,
-          } = this.state;
+          const { loadedKeys: currentLoadedKeys, loadingKeys: currentLoadingKeys } = this.state;
           const newLoadedKeys = arrAdd(currentLoadedKeys, key);
           const newLoadingKeys = arrDel(currentLoadingKeys, key);
 
@@ -813,10 +742,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       });
     });
 
-  onNodeExpand = (
-    e: React.MouseEvent<HTMLDivElement>,
-    treeNode: EventDataNode,
-  ) => {
+  onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     let { expandedKeys } = this.state;
     const { treeData } = this.state;
     const { onExpand, loadData } = this.props;
@@ -854,10 +780,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       return loadPromise
         ? loadPromise.then(() => {
             // [Legacy] Refresh logic
-            const newFlattenTreeData = flattenTreeData(
-              this.state.treeData,
-              expandedKeys,
-            );
+            const newFlattenTreeData = flattenTreeData(this.state.treeData, expandedKeys);
             this.setUncontrolledState({ flattenNodes: newFlattenTreeData });
           })
         : null;
@@ -866,30 +789,21 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return null;
   };
 
-  onNodeMouseEnter = (
-    event: React.MouseEvent<HTMLDivElement>,
-    node: EventDataNode,
-  ) => {
+  onNodeMouseEnter = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
     const { onMouseEnter } = this.props;
     if (onMouseEnter) {
       onMouseEnter({ event, node });
     }
   };
 
-  onNodeMouseLeave = (
-    event: React.MouseEvent<HTMLDivElement>,
-    node: EventDataNode,
-  ) => {
+  onNodeMouseLeave = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
     const { onMouseLeave } = this.props;
     if (onMouseLeave) {
       onMouseLeave({ event, node });
     }
   };
 
-  onNodeContextMenu = (
-    event: React.MouseEvent<HTMLDivElement>,
-    node: EventDataNode,
-  ) => {
+  onNodeContextMenu = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
     const { onRightClick } = this.props;
     if (onRightClick) {
       event.preventDefault();
@@ -942,16 +856,21 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   // =========================== Keyboard ===========================
-  onActiveChange = (activeKey: Key) => {
+  onActiveChange = (newActiveKey: Key) => {
+    const { activeKey } = this.state;
     const { onActiveChange } = this.props;
 
-    this.setState({ activeKey });
-    if (activeKey !== null) {
-      this.scrollTo({ key: activeKey });
+    if (activeKey === newActiveKey) {
+      return;
+    }
+
+    this.setState({ activeKey: newActiveKey });
+    if (newActiveKey !== null) {
+      this.scrollTo({ key: newActiveKey });
     }
 
     if (onActiveChange) {
-      onActiveChange(activeKey);
+      onActiveChange(newActiveKey);
     }
   };
 
@@ -967,9 +886,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   offsetActiveKey = (offset: number) => {
     const { flattenNodes, activeKey } = this.state;
 
-    let index = flattenNodes.findIndex(
-      ({ data: { key } }) => key === activeKey,
-    );
+    let index = flattenNodes.findIndex(({ data: { key } }) => key === activeKey);
 
     // Align with index
     if (index === -1 && offset < 0) {
@@ -1011,8 +928,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       const treeNodeRequiredProps = this.getTreeNodeRequiredProps();
 
       const expandable =
-        activeItem.data.isLeaf === false ||
-        !!(activeItem.data.children || []).length;
+        activeItem.data.isLeaf === false || !!(activeItem.data.children || []).length;
       const eventNode = convertNodePropsToEventData({
         ...getTreeNodeProps(activeKey, treeNodeRequiredProps),
         data: activeItem.data,
@@ -1024,10 +940,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         case KeyCode.LEFT: {
           // Collapse if possible
           if (expandable && expandedKeys.includes(activeKey)) {
-            this.onNodeExpand(
-              {} as React.MouseEvent<HTMLDivElement>,
-              eventNode,
-            );
+            this.onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
           } else if (activeItem.parent) {
             this.onActiveChange(activeItem.parent.data.key);
           }
@@ -1037,10 +950,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         case KeyCode.RIGHT: {
           // Expand if possible
           if (expandable && !expandedKeys.includes(activeKey)) {
-            this.onNodeExpand(
-              {} as React.MouseEvent<HTMLDivElement>,
-              eventNode,
-            );
+            this.onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
           } else if (activeItem.children && activeItem.children.length) {
             this.onActiveChange(activeItem.children[0].data.key);
           }
@@ -1068,10 +978,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
             !eventNode.disabled &&
             eventNode.selectable !== false
           ) {
-            this.onNodeSelect(
-              {} as React.MouseEvent<HTMLDivElement>,
-              eventNode,
-            );
+            this.onNodeSelect({} as React.MouseEvent<HTMLDivElement>, eventNode);
           }
           break;
         }
@@ -1086,10 +993,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   /**
    * Only update the value which is not in props
    */
-  setUncontrolledState = (
-    state: Partial<TreeState>,
-    atomic: boolean = false,
-  ) => {
+  setUncontrolledState = (state: Partial<TreeState>, atomic: boolean = false) => {
     let needSync = false;
     let allPassed = true;
     const newState = {};
@@ -1114,13 +1018,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   render() {
-    const {
-      focused,
-      flattenNodes,
-      keyEntities,
-      dragging,
-      activeKey,
-    } = this.state;
+    const { focused, flattenNodes, keyEntities, dragging, activeKey } = this.state;
     const {
       prefixCls,
       className,
@@ -1143,9 +1041,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       itemHeight,
       virtual,
     } = this.props;
-    const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(
-      this.props,
-    );
+    const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
 
     return (
       <TreeContext.Provider
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index db78520e..91e71bff 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -143,6 +143,21 @@ describe('Tree Accessibility', () => {
       // Blur
       wrapper.find('input').simulate('blur');
       expect(onBlur).toHaveBeenCalled();
+
+      // null activeKey
+      wrapper
+        .find('.rc-tree-treenode')
+        .first()
+        .simulate('mouseMove');
+      checkActiveTrigger(null);
+
+      for (let i = 0; i < 10; i += 1) {
+        wrapper
+          .find('.rc-tree-treenode')
+          .first()
+          .simulate('mouseMove');
+        expect(onActiveChange).not.toHaveBeenCalled();
+      }
     }
 
     it('onSelect', () => {

From a43e3dcd6438683a8e1ed38820f60f97345990a6 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 2 Apr 2020 16:21:15 +0800
Subject: [PATCH 076/397] v3.1.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index e86e3571..392d38c8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.2",
+  "version": "3.1.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 300e1bb6d485dbf03f2c91a53a7b87b0405e295d Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 9 Apr 2020 16:05:19 +0800
Subject: [PATCH 077/397] fix: Tree should not block update with
 `halfCheckedKeys` fix ant-design/ant-design#22589

---
 src/Tree.tsx | 24 ++++++++++++++++++------
 1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 4cf25aa8..b5549b73 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -687,10 +687,15 @@ class Tree extends React.Component<TreeProps, TreeState> {
         eventObj.checkedNodesPositions.push({ node, pos });
       });
 
-      this.setUncontrolledState({
-        checkedKeys,
-        halfCheckedKeys,
-      });
+      this.setUncontrolledState(
+        {
+          checkedKeys,
+        },
+        false,
+        {
+          halfCheckedKeys,
+        },
+      );
     }
 
     if (onCheck) {
@@ -993,7 +998,11 @@ class Tree extends React.Component<TreeProps, TreeState> {
   /**
    * Only update the value which is not in props
    */
-  setUncontrolledState = (state: Partial<TreeState>, atomic: boolean = false) => {
+  setUncontrolledState = (
+    state: Partial<TreeState>,
+    atomic: boolean = false,
+    forceState: Partial<TreeState> | null = null,
+  ) => {
     let needSync = false;
     let allPassed = true;
     const newState = {};
@@ -1009,7 +1018,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     });
 
     if (needSync && (!atomic || allPassed)) {
-      this.setState(newState);
+      this.setState({
+        ...newState,
+        ...forceState,
+      } as TreeState);
     }
   };
 

From 03b29e644f57ca193da3f83aeb84d7070caf5a98 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 9 Apr 2020 16:06:43 +0800
Subject: [PATCH 078/397] v3.1.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 392d38c8..bbea8447 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.3",
+  "version": "3.1.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 5752cb0938d41b9d14a80589cf240cb8b9bd28c1 Mon Sep 17 00:00:00 2001
From: Tom Xu <ycxzhkx@gmail.com>
Date: Fri, 10 Apr 2020 19:07:31 +0800
Subject: [PATCH 079/397] EventDataNode typescript (#304)

---
 src/interface.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/interface.tsx b/src/interface.tsx
index db85db6a..c7cd0b8f 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -18,7 +18,7 @@ export interface DataNode {
   style?: React.CSSProperties;
 }
 
-export interface EventDataNode extends Omit<DataNode, 'children'> {
+export interface EventDataNode extends DataNode {
   expanded: boolean;
   selected: boolean;
   checked: boolean;

From 4b4a351559af87740d4c80afccf5255f2419b3fb Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 10 Apr 2020 19:11:52 +0800
Subject: [PATCH 080/397] v3.1.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index bbea8447..974de591 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.4",
+  "version": "3.1.5",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 1c56e48188b9e1a42cf1f6b9b9d1dc5cb40602f1 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sun, 26 Apr 2020 21:58:17 +0800
Subject: [PATCH 081/397] fix: Dragging not update the tree virtual list

---
 src/Tree.tsx | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index b5549b73..138604ec 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -385,16 +385,20 @@ class Tree extends React.Component<TreeProps, TreeState> {
   }
 
   onNodeDragStart = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
-    const { expandedKeys, keyEntities } = this.state;
+    const { expandedKeys, keyEntities, treeData } = this.state;
     const { onDragStart } = this.props;
     const { eventKey } = node.props;
 
     this.dragNode = node;
 
+    const newExpandedKeys = arrDel(expandedKeys, eventKey);
+    const flattenNodes: FlattenNode[] = flattenTreeData(treeData, newExpandedKeys);
+
     this.setState({
       dragging: true,
       dragNodesKeys: getDragNodesKeys(eventKey, keyEntities),
-      expandedKeys: arrDel(expandedKeys, eventKey),
+      expandedKeys: newExpandedKeys,
+      flattenNodes,
     });
 
     if (onDragStart) {

From 43a893f96dd4785becc28056b70411cabfbee966 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Sun, 26 Apr 2020 22:23:46 +0800
Subject: [PATCH 082/397] fix: Drag should not works on nest children (#317)

* fix: Drag logic

* update test case

* update test case
---
 src/Tree.tsx       |   9 ++-
 tests/Tree.spec.js | 161 ++++++++++++++++-----------------------------
 2 files changed, 62 insertions(+), 108 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 138604ec..7d439713 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -414,11 +414,11 @@ class Tree extends React.Component<TreeProps, TreeState> {
    * But let's just keep it to avoid event trigger logic change.
    */
   onNodeDragEnter = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
-    const { expandedKeys, keyEntities } = this.state;
+    const { expandedKeys, keyEntities, dragNodesKeys } = this.state;
     const { onDragEnter } = this.props;
     const { pos, eventKey } = node.props;
 
-    if (!this.dragNode) return;
+    if (!this.dragNode || dragNodesKeys.indexOf(eventKey) !== -1) return;
 
     const dropPosition = calcDropPosition(event, node);
 
@@ -478,9 +478,14 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeDragOver = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+    const { dragNodesKeys } = this.state;
     const { onDragOver } = this.props;
     const { eventKey } = node.props;
 
+    if (dragNodesKeys.indexOf(eventKey) !== -1) {
+      return;
+    }
+
     // Update drag position
     if (this.dragNode && eventKey === this.state.dragOverNodeKey) {
       const dropPosition = calcDropPosition(event, node);
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 472ee7e8..2f80e6ab 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -14,6 +14,11 @@ const OPEN_CLASSNAME = '.rc-tree-switcher_open';
 const CHECKED_CLASSNAME = '.rc-tree-checkbox-checked';
 const SELECTED_CLASSNAME = '.rc-tree-node-selected';
 
+const delay = timeout =>
+  new Promise(resolve => {
+    setTimeout(resolve, timeout);
+  });
+
 describe('Tree Basic', () => {
   spyConsole();
 
@@ -225,14 +230,10 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      const firstCheckboxClassNames = wrapper
-        .find('.rc-tree-checkbox')
-        .instance().classList;
-      expect(
-        [].slice
-          .call(firstCheckboxClassNames)
-          .includes(CHECKED_CLASSNAME.slice(1)),
-      ).toBe(false);
+      const firstCheckboxClassNames = wrapper.find('.rc-tree-checkbox').instance().classList;
+      expect([].slice.call(firstCheckboxClassNames).includes(CHECKED_CLASSNAME.slice(1))).toBe(
+        false,
+      );
     });
 
     it('controlled by checkedKeys', () => {
@@ -451,21 +452,9 @@ describe('Tree Basic', () => {
         return (
           <Tree checkable onCheck={mockHandleCheck}>
             <TreeNode title="parent 1" key="0-0">
-              <TreeNode
-                title="leaf 1"
-                key="0-0-1"
-                disableCheckbox={disableCheckbox}
-              />
-              <TreeNode
-                title="leaf 2"
-                key="0-0-2"
-                disableCheckbox={disableCheckbox}
-              />
-              <TreeNode
-                title="leaf 3"
-                key="0-0-3"
-                disableCheckbox={disableCheckbox}
-              />
+              <TreeNode title="leaf 1" key="0-0-1" disableCheckbox={disableCheckbox} />
+              <TreeNode title="leaf 2" key="0-0-2" disableCheckbox={disableCheckbox} />
+              <TreeNode title="leaf 3" key="0-0-3" disableCheckbox={disableCheckbox} />
             </TreeNode>
           </Tree>
         );
@@ -535,9 +524,7 @@ describe('Tree Basic', () => {
       it('works', () => {
         const checkedKeys = ['0-0-0'];
         const wrapper = mount(<Tree checkable checkedKeys={checkedKeys} />);
-        expect(errorSpy()).toHaveBeenCalledWith(
-          "Warning: Tree missing follow keys: '0-0-0'",
-        );
+        expect(errorSpy()).toHaveBeenCalledWith("Warning: Tree missing follow keys: '0-0-0'");
 
         wrapper.setProps({
           expandedKeys: ['0-0'],
@@ -694,9 +681,7 @@ describe('Tree Basic', () => {
       firstNode().simulate('click');
 
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(
-        false,
-      );
+      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
       expect(
         wrapper
           .state()
@@ -706,9 +691,7 @@ describe('Tree Basic', () => {
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(
-        false,
-      );
+      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
       expect(wrapper.state().checkedKeys).toEqual([]);
     });
 
@@ -735,9 +718,7 @@ describe('Tree Basic', () => {
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(
-        false,
-      );
+      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
       expect(
         wrapper
           .state()
@@ -747,9 +728,7 @@ describe('Tree Basic', () => {
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(
-        false,
-      );
+      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
       expect(wrapper.state().checkedKeys).toEqual([]);
     });
 
@@ -782,15 +761,11 @@ describe('Tree Basic', () => {
 
       getNode().simulate('click');
       expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(
-        false,
-      );
+      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
       expect(getParent().hasClass('rc-tree-checkbox-checked')).toBe(true);
       getNode().simulate('click');
       expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
-      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(
-        false,
-      );
+      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
       expect(getParent().hasClass('rc-tree-checkbox-checked')).toBe(false);
     });
   });
@@ -804,9 +779,7 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      expect(
-        wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME),
-      ).toBe(true);
+      expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(true);
     });
 
     it('controlled by selectedKeys', () => {
@@ -817,13 +790,9 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      expect(
-        wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME),
-      ).toBe(false);
+      expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(false);
       wrapper.setProps({ selectedKeys: ['0-0'] });
-      expect(
-        wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME),
-      ).toBe(true);
+      expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(true);
     });
 
     it('fires select event', () => {
@@ -950,9 +919,7 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
-    const node = convertNodePropsToEventData(
-      wrapper.find(InternalTreeNode).props(),
-    );
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
     wrapper.find('.rc-tree-node-content-wrapper').simulate('contextMenu');
     expect(handleRightClick.mock.calls[0][0].node).toEqual(node);
   });
@@ -979,9 +946,7 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
-    const node = convertNodePropsToEventData(
-      wrapper.find(InternalTreeNode).props(),
-    );
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
     wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseEnter');
     expect(handleMouseEnter.mock.calls[0][0].node).toEqual(node);
   });
@@ -995,9 +960,7 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
-    const node = convertNodePropsToEventData(
-      wrapper.find(InternalTreeNode).props(),
-    );
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
     wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseLeave');
     expect(handleMouseLeave.mock.calls[0][0].node).toEqual(node);
   });
@@ -1034,9 +997,7 @@ describe('Tree Basic', () => {
         <TreeNode title="parent 1" key="0-0" />
       </Tree>,
     );
-    const node = convertNodePropsToEventData(
-      wrapper.find(InternalTreeNode).props(),
-    );
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
     wrapper.find('.rc-tree-switcher').simulate('click');
     expect(loadData).toHaveBeenCalledWith(node);
     expect(then).toHaveBeenCalled();
@@ -1057,9 +1018,7 @@ describe('Tree Basic', () => {
     it('fires dragStart event', () => {
       const onDragStart = jest.fn();
       const wrapper = mount(createTree({ onDragStart }));
-      const treeNode = wrapper.find(
-        '.dragTarget > .rc-tree-node-content-wrapper',
-      );
+      const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
       const node = convertNodePropsToEventData(
         wrapper
           .find(InternalTreeNode)
@@ -1071,31 +1030,35 @@ describe('Tree Basic', () => {
       expect(event.node).toEqual(node);
     });
 
-    it('fires dragEnter event', done => {
+    it('fires dragEnter event', async () => {
       const onDragEnter = jest.fn();
       const wrapper = mount(createTree({ onDragEnter }));
 
-      wrapper
-        .find('.dragTarget > .rc-tree-node-content-wrapper')
-        .simulate('dragStart');
+      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+
+      // Not trigger self
+      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
+      await delay(500);
+      expect(onDragEnter).not.toHaveBeenCalled();
+
       wrapper
         .find('.dropTarget')
         .at(0)
         .simulate('dragEnter');
       expect(onDragEnter).not.toHaveBeenCalled();
-      setTimeout(() => {
-        wrapper.update();
-        const node = convertNodePropsToEventData(
-          wrapper
-            .find(InternalTreeNode)
-            .at(2)
-            .props(),
-        );
-        const event = onDragEnter.mock.calls[0][0];
-        expect(event.node).toEqual(node);
-        expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
-        done();
-      }, 500);
+
+      await delay(500);
+      wrapper.update();
+      const node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(2)
+          .props(),
+      );
+      const event = onDragEnter.mock.calls[0][0];
+      expect(event.node).toEqual(node);
+      expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
+      expect(onDragEnter).toHaveBeenCalledTimes(1);
     });
 
     it('fires dragOver event', () => {
@@ -1136,9 +1099,7 @@ describe('Tree Basic', () => {
     it('fires drop event', () => {
       const onDrop = jest.fn();
       const wrapper = mount(createTree({ onDrop }));
-      wrapper
-        .find('.dragTarget > .rc-tree-node-content-wrapper')
-        .simulate('dragStart');
+      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
       const dropNode = convertNodePropsToEventData(
         wrapper
           .find(InternalTreeNode)
@@ -1170,9 +1131,7 @@ describe('Tree Basic', () => {
           .at(1)
           .props(),
       );
-      wrapper
-        .find('.dragTarget > .rc-tree-node-content-wrapper')
-        .simulate('dragEnd');
+      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
       const event = onDragEnd.mock.calls[0][0];
       expect(event.node).toEqual(node);
     });
@@ -1188,9 +1147,7 @@ describe('Tree Basic', () => {
       const dragTree = wrapper.find(Tree).at(0);
       const normalTree = wrapper.find(Tree).at(1);
 
-      dragTree
-        .find('.dragTarget > .rc-tree-node-content-wrapper')
-        .simulate('dragStart');
+      dragTree.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
       normalTree
         .find('.dropTarget')
         .at(0)
@@ -1219,9 +1176,7 @@ describe('Tree Basic', () => {
             </Tree>,
           );
 
-          wrapper
-            .find('.dragTarget > .rc-tree-node-content-wrapper')
-            .simulate('dragStart');
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
 
           // 1. Move into target (first in the middle of the node)
           wrapper.find(targetSelector).simulate('dragEnter', { clientY: 10 });
@@ -1234,9 +1189,7 @@ describe('Tree Basic', () => {
             // 3. Move in again
             wrapper.find(targetSelector).simulate('dragEnter', { clientY: 0 });
             setTimeout(() => {
-              wrapper
-                .find(targetSelector)
-                .simulate('dragOver', { clientY: 999 });
+              wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
 
               // 4. Drop
               wrapper.find(targetSelector).simulate('drop');
@@ -1353,9 +1306,7 @@ describe('Tree Basic', () => {
             setCheckedKeys(keys);
             onCheck(keys);
           }}
-          treeData={[
-            { key: 11, title: 11, children: [{ key: 22, title: 22 }] },
-          ]}
+          treeData={[{ key: 11, title: 11, children: [{ key: 22, title: 22 }] }]}
         />
       );
     };
@@ -1439,9 +1390,7 @@ describe('Tree Basic', () => {
       });
     }
 
-    const wrapper = mount(
-      <Tree itemHeight={10} height={100} treeData={data} virtual={false} />,
-    );
+    const wrapper = mount(<Tree itemHeight={10} height={100} treeData={data} virtual={false} />);
 
     expect(wrapper.find('List').props().virtual).toBe(false);
   });

From 710f32502a446cc4c9213330e320e58186736c18 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sun, 26 Apr 2020 22:25:51 +0800
Subject: [PATCH 083/397] 3.1.6

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 974de591..f17cbeb4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.5",
+  "version": "3.1.6",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 0a11cd63c4cee4d3cc426d446da7639db38e141f Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 4 May 2020 19:54:09 +0800
Subject: [PATCH 084/397] fix: Tree unctrolled drag expandedKeys fix
 https://github.com/ant-design/ant-design/issues/23851

---
 src/Tree.tsx | 29 +++++++++++++++++++----------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 7d439713..f2f38889 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -385,22 +385,21 @@ class Tree extends React.Component<TreeProps, TreeState> {
   }
 
   onNodeDragStart = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
-    const { expandedKeys, keyEntities, treeData } = this.state;
+    const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
     const { eventKey } = node.props;
 
     this.dragNode = node;
 
     const newExpandedKeys = arrDel(expandedKeys, eventKey);
-    const flattenNodes: FlattenNode[] = flattenTreeData(treeData, newExpandedKeys);
 
     this.setState({
       dragging: true,
       dragNodesKeys: getDragNodesKeys(eventKey, keyEntities),
-      expandedKeys: newExpandedKeys,
-      flattenNodes,
     });
 
+    this.setExpandedKeys(newExpandedKeys);
+
     if (onDragStart) {
       onDragStart({ event, node: convertNodePropsToEventData(node.props) });
     }
@@ -461,9 +460,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         }
 
         if (!('expandedKeys' in this.props)) {
-          this.setState({
-            expandedKeys: newExpandedKeys,
-          });
+          this.setExpandedKeys(newExpandedKeys);
         }
 
         if (onDragEnter) {
@@ -758,7 +755,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     let { expandedKeys } = this.state;
-    const { treeData } = this.state;
     const { onExpand, loadData } = this.props;
     const { key, expanded } = treeNode;
 
@@ -777,8 +773,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       expandedKeys = arrDel(expandedKeys, key);
     }
 
-    const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys);
-    this.setUncontrolledState({ expandedKeys, flattenNodes }, true);
+    this.setExpandedKeys(expandedKeys);
 
     if (onExpand) {
       onExpand(expandedKeys, {
@@ -1004,6 +999,20 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
+  /** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
+  setExpandedKeys = (expandedKeys: Key[]) => {
+    const { treeData } = this.state;
+
+    const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys);
+    this.setUncontrolledState(
+      {
+        expandedKeys,
+        flattenNodes,
+      },
+      true,
+    );
+  };
+
   /**
    * Only update the value which is not in props
    */

From 3c4e5458e72cdee5c3d78a033350c7285b733554 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 4 May 2020 20:05:05 +0800
Subject: [PATCH 085/397] 3.1.7

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f17cbeb4..183ad050 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.6",
+  "version": "3.1.7",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 9d241477cc729160fc0802bb65405481ce2e23ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 4 May 2020 23:16:48 +0800
Subject: [PATCH 086/397] chore: Lock expand when in motion (#322)

* lock when motion is in progress

* add test case
---
 package.json             |   1 +
 src/NodeList.tsx         |  35 +++++-----
 src/Tree.tsx             | 142 +++++++++++++++++++++++----------------
 tests/TreeMotion.spec.js |  60 ++++++++++++++++-
 tests/TreeProps.spec.js  |  20 +-----
 5 files changed, 162 insertions(+), 96 deletions(-)

diff --git a/package.json b/package.json
index 183ad050..bbacdd5e 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
     "react-dom": "*"
   },
   "devDependencies": {
+    "@types/jest": "^25.2.1",
     "@types/react": "^16.8.19",
     "@types/react-dom": "^16.8.4",
     "@types/warning": "^3.0.0",
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index dcbc98db..d94c8b91 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -84,6 +84,9 @@ interface NodeListProps {
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
   onActiveChange: (key: Key) => void;
+
+  onListChangeStart: () => void;
+  onListChangeEnd: () => void;
 }
 
 /**
@@ -121,10 +124,7 @@ function getAccessibilityPath(item: FlattenNode): string {
   return path;
 }
 
-const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
-  props,
-  ref,
-) => {
+const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (props, ref) => {
   const {
     prefixCls,
     data,
@@ -158,6 +158,9 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
     onBlur,
     onActiveChange,
 
+    onListChangeStart,
+    onListChangeEnd,
+
     ...domProps
   } = props;
 
@@ -175,9 +178,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
   const [prevData, setPrevData] = React.useState(data);
   const [transitionData, setTransitionData] = React.useState(data);
   const [transitionRange, setTransitionRange] = React.useState([]);
-  const [motionType, setMotionType] = React.useState<'show' | 'hide' | null>(
-    null,
-  );
+  const [motionType, setMotionType] = React.useState<'show' | 'hide' | null>(null);
 
   function onMotionEnd() {
     setPrevData(data);
@@ -185,6 +186,8 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
     setTransitionRange([]);
     setMotionType(null);
     setDisableVirtual(false);
+
+    onListChangeEnd();
   }
 
   // Do animation if expanded keys changed
@@ -195,9 +198,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
 
     if (diffExpanded.key !== null) {
       if (diffExpanded.add) {
-        const keyIndex = prevData.findIndex(
-          ({ data: { key } }) => key === diffExpanded.key,
-        );
+        const keyIndex = prevData.findIndex(({ data: { key } }) => key === diffExpanded.key);
 
         if (motion) setDisableVirtual(true);
         const rangeNodes = getMinimumRangeTransitionRange(
@@ -213,9 +214,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
         setTransitionRange(rangeNodes);
         setMotionType('show');
       } else {
-        const keyIndex = data.findIndex(
-          ({ data: { key } }) => key === diffExpanded.key,
-        );
+        const keyIndex = data.findIndex(({ data: { key } }) => key === diffExpanded.key);
 
         if (motion) setDisableVirtual(true);
         const rangeNodes = getMinimumRangeTransitionRange(
@@ -231,6 +230,11 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
         setTransitionRange(rangeNodes);
         setMotionType('hide');
       }
+
+      // Trigger when `motion` provided
+      if (motion) {
+        onListChangeStart();
+      }
     } else if (prevData !== data) {
       // If whole data changed, we just refresh the list
       setPrevData(data);
@@ -303,10 +307,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (
           const mergedKey = getKey(key, pos);
           delete restProps.children;
 
-          const treeNodeProps = getTreeNodeProps(
-            mergedKey,
-            treeNodeRequiredProps,
-          );
+          const treeNodeProps = getTreeNodeProps(mergedKey, treeNodeRequiredProps);
 
           return (
             <MotionTreeNode
diff --git a/src/Tree.tsx b/src/Tree.tsx
index f2f38889..19bafc8a 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -167,6 +167,9 @@ interface TreeState {
   focused: boolean;
   activeKey: Key;
 
+  // Record if list is changing
+  listChanging: boolean;
+
   prevProps: TreeProps;
 }
 
@@ -263,6 +266,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
     focused: false,
     activeKey: null,
 
+    listChanging: false,
+
     prevProps: null,
   };
 
@@ -753,51 +758,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
       });
     });
 
-  onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
-    let { expandedKeys } = this.state;
-    const { onExpand, loadData } = this.props;
-    const { key, expanded } = treeNode;
-
-    // Update selected keys
-    const index = expandedKeys.indexOf(key);
-    const targetExpanded = !expanded;
-
-    warning(
-      (expanded && index !== -1) || (!expanded && index === -1),
-      'Expand state not sync with index check',
-    );
-
-    if (targetExpanded) {
-      expandedKeys = arrAdd(expandedKeys, key);
-    } else {
-      expandedKeys = arrDel(expandedKeys, key);
-    }
-
-    this.setExpandedKeys(expandedKeys);
-
-    if (onExpand) {
-      onExpand(expandedKeys, {
-        node: treeNode,
-        expanded: targetExpanded,
-        nativeEvent: e.nativeEvent,
-      });
-    }
-
-    // Async Load data
-    if (targetExpanded && loadData) {
-      const loadPromise = this.onNodeLoad(treeNode);
-      return loadPromise
-        ? loadPromise.then(() => {
-            // [Legacy] Refresh logic
-            const newFlattenTreeData = flattenTreeData(this.state.treeData, expandedKeys);
-            this.setUncontrolledState({ flattenNodes: newFlattenTreeData });
-          })
-        : null;
-    }
-
-    return null;
-  };
-
   onNodeMouseEnter = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
     const { onMouseEnter } = this.props;
     if (onMouseEnter) {
@@ -864,6 +824,82 @@ class Tree extends React.Component<TreeProps, TreeState> {
     };
   };
 
+  // =========================== Expanded ===========================
+  /** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
+  setExpandedKeys = (expandedKeys: Key[]) => {
+    const { treeData } = this.state;
+
+    const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys);
+    this.setUncontrolledState(
+      {
+        expandedKeys,
+        flattenNodes,
+      },
+      true,
+    );
+  };
+
+  onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
+    let { expandedKeys } = this.state;
+    const { listChanging } = this.state;
+    const { onExpand, loadData } = this.props;
+    const { key, expanded } = treeNode;
+
+    // Do nothing when motion is in progress
+    if (listChanging) {
+      return;
+    }
+
+    // Update selected keys
+    const index = expandedKeys.indexOf(key);
+    const targetExpanded = !expanded;
+
+    warning(
+      (expanded && index !== -1) || (!expanded && index === -1),
+      'Expand state not sync with index check',
+    );
+
+    if (targetExpanded) {
+      expandedKeys = arrAdd(expandedKeys, key);
+    } else {
+      expandedKeys = arrDel(expandedKeys, key);
+    }
+
+    this.setExpandedKeys(expandedKeys);
+
+    if (onExpand) {
+      onExpand(expandedKeys, {
+        node: treeNode,
+        expanded: targetExpanded,
+        nativeEvent: e.nativeEvent,
+      });
+    }
+
+    // Async Load data
+    if (targetExpanded && loadData) {
+      const loadPromise = this.onNodeLoad(treeNode);
+      if (loadPromise) {
+        loadPromise.then(() => {
+          // [Legacy] Refresh logic
+          const newFlattenTreeData = flattenTreeData(this.state.treeData, expandedKeys);
+          this.setUncontrolledState({ flattenNodes: newFlattenTreeData });
+        });
+      }
+    }
+  };
+
+  onListChangeStart = () => {
+    this.setUncontrolledState({
+      listChanging: true,
+    });
+  };
+
+  onListChangeEnd = () => {
+    this.setUncontrolledState({
+      listChanging: false,
+    });
+  };
+
   // =========================== Keyboard ===========================
   onActiveChange = (newActiveKey: Key) => {
     const { activeKey } = this.state;
@@ -999,20 +1035,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  /** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
-  setExpandedKeys = (expandedKeys: Key[]) => {
-    const { treeData } = this.state;
-
-    const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys);
-    this.setUncontrolledState(
-      {
-        expandedKeys,
-        flattenNodes,
-      },
-      true,
-    );
-  };
-
   /**
    * Only update the value which is not in props
    */
@@ -1135,6 +1157,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
             onBlur={this.onBlur}
             onKeyDown={this.onKeyDown}
             onActiveChange={this.onActiveChange}
+            onListChangeStart={this.onListChangeStart}
+            onListChangeEnd={this.onListChangeEnd}
             {...this.getTreeNodeRequiredProps()}
             {...domProps}
           />
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.js
index c5df3ef1..20ff25fa 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.js
@@ -2,10 +2,28 @@ import React from 'react';
 import { mount } from 'enzyme';
 import { act } from 'react-dom/test-utils';
 import VirtualList from 'rc-virtual-list';
-import Tree from '../src';
+import Tree, { TreeNode } from '../src';
 import { getMinimumRangeTransitionRange } from '../src/NodeList';
 
 describe('Tree Motion', () => {
+  it('basic', () => {
+    const motion = {
+      motionName: 'bamboo',
+    };
+    const wrapper = mount(
+      <Tree motion={motion}>
+        <TreeNode key="0-0">
+          <TreeNode key="0-0-0" />
+        </TreeNode>
+      </Tree>,
+    );
+
+    const switcher = wrapper.find('.rc-tree-switcher');
+    switcher.simulate('click');
+
+    expect(wrapper.find('CSSMotion').props()).toMatchObject(motion);
+  });
+
   it('hide item', done => {
     const wrapper = mount(
       <Tree
@@ -70,4 +88,44 @@ describe('Tree Motion', () => {
 
     wrapper.setProps({ treeData: [] });
   });
+
+  it('should not expanded when in motion', () => {
+    const raf = jest
+      .spyOn(window, 'requestAnimationFrame')
+      .mockImplementation(fn => window.setTimeout(fn, 16));
+    jest.useFakeTimers();
+
+    const onExpand = jest.fn();
+    const wrapper = mount(
+      <Tree
+        onExpand={onExpand}
+        motion={{
+          motionName: 'bamboo',
+          motionDeadline: 1000,
+          motionAppear: true,
+        }}
+      >
+        <TreeNode key="0-0">
+          <TreeNode key="0-0-0" />
+        </TreeNode>
+      </Tree>,
+    );
+
+    function doExpand() {
+      const switcher = wrapper.find('.rc-tree-switcher').first();
+      switcher.simulate('click');
+    }
+
+    // First click should work
+    doExpand();
+    expect(onExpand).toHaveBeenCalled();
+    onExpand.mockReset();
+
+    // Not trigger when in motion
+    doExpand();
+    expect(onExpand).not.toHaveBeenCalled();
+
+    raf.mockRestore();
+    jest.useRealTimers();
+  });
 });
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index 3304d69b..2fce6d7e 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -3,11 +3,11 @@ import React from 'react';
 import { render, mount } from 'enzyme';
 import { renderToJson } from 'enzyme-to-json';
 import PropTypes from 'prop-types';
+import { resetWarned } from 'rc-util/lib/warning';
 import Tree, { TreeNode } from '../src';
 import { InternalTreeNode } from '../src/TreeNode';
 import { objectMatcher, spyConsole, spyError } from './util';
 import { convertNodePropsToEventData } from '../src/utils/treeUtil';
-import { resetWarned } from 'rc-util/lib/warning';
 
 /**
  * For refactor purpose. All the props should be passed by test
@@ -836,24 +836,6 @@ describe('Tree Props', () => {
     });
   });
 
-  it('motion', () => {
-    const motion = {
-      motionName: 'bamboo',
-    };
-    const wrapper = mount(
-      <Tree motion={motion}>
-        <TreeNode key="0-0">
-          <TreeNode key="0-0-0" />
-        </TreeNode>
-      </Tree>,
-    );
-
-    const switcher = wrapper.find('.rc-tree-switcher');
-    switcher.simulate('click');
-
-    expect(wrapper.find('CSSMotion').props()).toMatchObject(motion);
-  });
-
   describe('data and aria props', () => {
     it('renders data attributes', () => {
       const wrapper = render(<Tree data-test="tree" />);

From 229c31cce6ad117285ea7e66b89cebba872dbbcb Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 4 May 2020 23:18:09 +0800
Subject: [PATCH 087/397] 3.1.8

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index bbacdd5e..2bd02a3c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.7",
+  "version": "3.1.8",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 2c7e85b36e77c746c704850abe311e96fad024f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=81=8F=E5=8F=B3?= <afc163@gmail.com>
Date: Mon, 4 May 2020 23:31:23 +0800
Subject: [PATCH 088/397] Update README.md

---
 README.md | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index c161583c..5da49ae2 100644
--- a/README.md
+++ b/README.md
@@ -2,22 +2,30 @@
 
 ---
 
-tree ui component for react
+Tree component.
 
-[![NPM version][npm-image]][npm-url] [![build status][travis-image]][travis-url] [![Test coverage][codecov-image]][codecov-url] [![gemnasium deps][gemnasium-image]][gemnasium-url] [![npm download][download-image]][download-url]
+[![NPM version][npm-image]][npm-url]
+[![build status][travis-image]][travis-url]
+[![Test coverage][coveralls-image]][coveralls-url]
+[![Dependencies][david-image]][david-url]
+[![DevDependencies][david-dev-image]][david-dev-url]
+[![npm download][download-image]][download-url]
+[![bundle size][bundlephobia-image]][bundlephobia-url]
 
 [npm-image]: http://img.shields.io/npm/v/rc-tree.svg?style=flat-square
 [npm-url]: http://npmjs.org/package/rc-tree
 [travis-image]: https://img.shields.io/travis/react-component/tree.svg?style=flat-square
 [travis-url]: https://travis-ci.org/react-component/tree
-[codecov-image]: https://img.shields.io/codecov/c/github/react-component/tree/master.svg?style=flat-square
-[codecov-url]: https://codecov.io/gh/react-component/tree/branch/master
-[gemnasium-image]: http://img.shields.io/gemnasium/react-component/tree.svg?style=flat-square
-[gemnasium-url]: https://gemnasium.com/react-component/tree
-[node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square
-[node-url]: http://nodejs.org/download/
+[coveralls-image]: https://img.shields.io/coveralls/react-component/tree.svg?style=flat-square
+[coveralls-url]: https://coveralls.io/r/react-component/tree?branch=master
+[david-url]: https://david-dm.org/react-component/tree
+[david-image]: https://david-dm.org/react-component/tree/status.svg?style=flat-square
+[david-dev-url]: https://david-dm.org/react-component/tree?type=dev
+[david-dev-image]: https://david-dm.org/react-component/tree/dev-status.svg?style=flat-square
 [download-image]: https://img.shields.io/npm/dm/rc-tree.svg?style=flat-square
 [download-url]: https://npmjs.org/package/rc-tree
+[bundlephobia-url]: https://bundlephobia.com/result?p=rc-tree
+[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-tree
 
 ## Screenshots
 

From 1a783fda3c411e1be75fb5c4edbb09792d479b6e Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 4 May 2020 23:31:39 +0800
Subject: [PATCH 089/397] chore: Make lock expand async

---
 src/Tree.tsx | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 19bafc8a..fbf3db19 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -895,8 +895,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onListChangeEnd = () => {
-    this.setUncontrolledState({
-      listChanging: false,
+    setTimeout(() => {
+      this.setUncontrolledState({
+        listChanging: false,
+      });
     });
   };
 

From 75ca759425eab78d4af28c5cc596de0402d05f2e Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 4 May 2020 23:33:40 +0800
Subject: [PATCH 090/397] 3.1.9

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2bd02a3c..0a501551 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.8",
+  "version": "3.1.9",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 3281cf9b94dafad67d05910e6f9b7ef782170e57 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Fri, 8 May 2020 11:29:40 +0800
Subject: [PATCH 091/397] chore(deps): bump rc-animate from 2.11.1 to 3.0.0
 (#324)

Bumps [rc-animate](https://github.com/react-component/animate) from 2.11.1 to 3.0.0.
- [Release notes](https://github.com/react-component/animate/releases)
- [Changelog](https://github.com/react-component/animate/blob/master/HISTORY.md)
- [Commits](https://github.com/react-component/animate/compare/2.11.1...3.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0a501551..18886887 100644
--- a/package.json
+++ b/package.json
@@ -69,7 +69,7 @@
   "dependencies": {
     "classnames": "2.x",
     "prop-types": "^15.5.8",
-    "rc-animate": "^2.9.2",
+    "rc-animate": "^3.0.0",
     "rc-util": "^4.11.0",
     "rc-virtual-list": "^1.1.0",
     "react-lifecycles-compat": "^3.0.4"

From 7ecfef851fc7987cbf71db2773f38fd33a2b541d Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Fri, 8 May 2020 11:43:20 +0800
Subject: [PATCH 092/397] Remove `react-lifecycles-compat` and `prop-types`

---
 HISTORY.md                      |  5 ++++
 examples/basic-controlled.jsx   |  5 ----
 examples/basic.jsx              |  5 ----
 examples/big-data-generator.js  |  8 -----
 examples/big-data.jsx           |  5 ----
 examples/custom-switch-icon.jsx |  5 ----
 examples/dropdown.jsx           | 17 -----------
 examples/icon.jsx               |  4 ---
 package.json                    |  5 ++--
 src/Tree.tsx                    | 53 ---------------------------------
 src/TreeNode.tsx                | 34 ---------------------
 tests/TreeProps.spec.js         |  5 ----
 12 files changed, 7 insertions(+), 144 deletions(-)

diff --git a/HISTORY.md b/HISTORY.md
index 407250b1..4004aede 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,6 +1,11 @@
 # History
 ---
 
+## 3.2.0 `2020-05-08`
+
+- Upgrade `rc-animate`.
+- Remove `react-lifecycles-compat` and `prop-types`.
+
 ## 2.1.0 / 2019-04-28
 
 - TreeNode support `checkable`.
diff --git a/examples/basic-controlled.jsx b/examples/basic-controlled.jsx
index a816a7e6..18c3a64e 100644
--- a/examples/basic-controlled.jsx
+++ b/examples/basic-controlled.jsx
@@ -1,17 +1,12 @@
 /* eslint-disable no-console, react/no-unescaped-entities */
 import '../assets/index.less';
 import React from 'react';
-import PropTypes from 'prop-types';
 import 'rc-dialog/assets/index.css';
 import Modal from 'rc-dialog';
 import Tree, { TreeNode } from '../src';
 import { gData, getRadioSelectKeys } from './utils/dataUtil';
 
 class Demo extends React.Component {
-  static propTypes = {
-    multiple: PropTypes.bool,
-  };
-
   static defaultProps = {
     multiple: true,
   };
diff --git a/examples/basic.jsx b/examples/basic.jsx
index 5e58d200..b6dbe859 100644
--- a/examples/basic.jsx
+++ b/examples/basic.jsx
@@ -1,7 +1,6 @@
 /* eslint-disable jsx-a11y/no-noninteractive-element-interactions,
 no-alert, no-console, react/no-find-dom-node */
 import React from 'react';
-import PropTypes from 'prop-types';
 import '../assets/index.less';
 import './basic.less';
 import Tree, { TreeNode } from '../src';
@@ -34,10 +33,6 @@ const treeData = [
 ];
 
 class Demo extends React.Component {
-  static propTypes = {
-    keys: PropTypes.array,
-  };
-
   static defaultProps = {
     keys: ['0-0-0-0'],
   };
diff --git a/examples/big-data-generator.js b/examples/big-data-generator.js
index de4f2fed..f87d3028 100644
--- a/examples/big-data-generator.js
+++ b/examples/big-data-generator.js
@@ -1,17 +1,9 @@
 /* eslint react/no-string-refs:0 */
 
 import React from 'react';
-import PropTypes from 'prop-types';
 import { generateData, calcTotal } from './utils/dataUtil';
 
 class Gen extends React.Component {
-  static propTypes = {
-    onGen: PropTypes.func,
-    x: PropTypes.number,
-    y: PropTypes.number,
-    z: PropTypes.number,
-  };
-
   static defaultProps = {
     onGen: () => {},
     x: 20,
diff --git a/examples/big-data.jsx b/examples/big-data.jsx
index d8ae9f1a..1ceba583 100644
--- a/examples/big-data.jsx
+++ b/examples/big-data.jsx
@@ -1,15 +1,10 @@
 /* eslint-disable no-console, prefer-destructuring */
 import React from 'react';
-import PropTypes from 'prop-types';
 import Gen from './big-data-generator';
 import '../assets/index.less';
 import Tree, { TreeNode } from '../src';
 
 class Demo extends React.Component {
-  static propTypes = {
-    multiple: PropTypes.bool,
-  };
-
   state = {
     gData: [],
     expandedKeys: [],
diff --git a/examples/custom-switch-icon.jsx b/examples/custom-switch-icon.jsx
index 1f8d16b4..1af9e1ac 100644
--- a/examples/custom-switch-icon.jsx
+++ b/examples/custom-switch-icon.jsx
@@ -2,7 +2,6 @@
 /* eslint no-alert:0 */
 /* eslint jsx-a11y/no-noninteractive-element-interactions:0 */
 import React from 'react';
-import PropTypes from 'prop-types';
 import '../assets/index.less';
 import Tree, { TreeNode } from '../src';
 
@@ -27,10 +26,6 @@ const getSvgIcon = (path, iStyle = {}, style = {}) => (
 );
 
 class Demo extends React.Component {
-  static propTypes = {
-    keys: PropTypes.array,
-  };
-
   static defaultProps = {
     keys: ['0-0-0-0'],
   };
diff --git a/examples/dropdown.jsx b/examples/dropdown.jsx
index a9b236aa..ebb9bbfc 100644
--- a/examples/dropdown.jsx
+++ b/examples/dropdown.jsx
@@ -2,7 +2,6 @@
 /* eslint no-console:0 */
 /* eslint react/no-string-refs:0 */
 import React from 'react';
-import PropTypes from 'prop-types';
 import Trigger from 'rc-trigger';
 import { gData } from './utils/dataUtil';
 import './dropdown.less';
@@ -30,22 +29,6 @@ const placements = {
   },
 };
 class DropdownTree extends React.Component {
-  static propTypes = {
-    onVisibleChange: PropTypes.func,
-    prefixCls: PropTypes.string,
-    children: PropTypes.any,
-    transitionName: PropTypes.string,
-    overlay: PropTypes.object,
-    overlayClassName: PropTypes.string,
-    animation: PropTypes.any,
-    align: PropTypes.object,
-    overlayStyle: PropTypes.object,
-    placement: PropTypes.string,
-    trigger: PropTypes.array,
-    defaultVisible: PropTypes.bool,
-    visible: PropTypes.bool,
-  };
-
   static defaultProps = {
     prefixCls: 'demo-dropdown-tree',
     trigger: ['hover'],
diff --git a/examples/icon.jsx b/examples/icon.jsx
index 9072fac4..55cda45b 100644
--- a/examples/icon.jsx
+++ b/examples/icon.jsx
@@ -1,7 +1,6 @@
 /* eslint no-console:0 */
 /* eslint no-alert:0 */
 import React from 'react';
-import PropTypes from 'prop-types';
 import classNames from 'classnames';
 import Tree, { TreeNode } from '../src';
 import '../assets/index.less';
@@ -10,9 +9,6 @@ import './icon.less';
 const Icon = ({ selected }) => (
   <span className={classNames('customize-icon', selected && 'selected-icon')} />
 );
-Icon.propTypes = {
-  selected: PropTypes.bool,
-};
 
 const Demo = () => (
   <div>
diff --git a/package.json b/package.json
index 18886887..bb1d8588 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",
     "enzyme-to-json": "^3.0.0",
+    "eslint": "^6.8.0",
     "father": "^2.25.0-alpha.1",
     "less": "^3.11.1",
     "np": "^6.0.0",
@@ -68,10 +69,8 @@
   },
   "dependencies": {
     "classnames": "2.x",
-    "prop-types": "^15.5.8",
     "rc-animate": "^3.0.0",
     "rc-util": "^4.11.0",
-    "rc-virtual-list": "^1.1.0",
-    "react-lifecycles-compat": "^3.0.4"
+    "rc-virtual-list": "^1.1.0"
   }
 }
diff --git a/src/Tree.tsx b/src/Tree.tsx
index fbf3db19..00f7b6c7 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -2,11 +2,9 @@
 // Fully accessibility support
 
 import * as React from 'react';
-import PropTypes from 'prop-types';
 import KeyCode from 'rc-util/lib/KeyCode';
 import warning from 'rc-util/lib/warning';
 import classNames from 'classnames';
-import { polyfill } from 'react-lifecycles-compat';
 
 import { TreeContext } from './contextTypes';
 import {
@@ -173,56 +171,7 @@ interface TreeState {
   prevProps: TreeProps;
 }
 
-const keyPropType = PropTypes.oneOfType([PropTypes.string, PropTypes.number]);
-
 class Tree extends React.Component<TreeProps, TreeState> {
-  static propTypes = {
-    prefixCls: PropTypes.string,
-    className: PropTypes.string,
-    style: PropTypes.object,
-    tabIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
-    children: PropTypes.any,
-    treeData: PropTypes.array, // Generate treeNode by children
-    showLine: PropTypes.bool,
-    showIcon: PropTypes.bool,
-    icon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-    selectable: PropTypes.bool,
-    disabled: PropTypes.bool,
-    multiple: PropTypes.bool,
-    checkable: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]),
-    checkStrictly: PropTypes.bool,
-    draggable: PropTypes.bool,
-    defaultExpandParent: PropTypes.bool,
-    autoExpandParent: PropTypes.bool,
-    defaultExpandAll: PropTypes.bool,
-    defaultExpandedKeys: PropTypes.arrayOf(keyPropType),
-    expandedKeys: PropTypes.arrayOf(keyPropType),
-    defaultCheckedKeys: PropTypes.arrayOf(keyPropType),
-    checkedKeys: PropTypes.oneOfType([PropTypes.arrayOf(keyPropType), PropTypes.object]),
-    defaultSelectedKeys: PropTypes.arrayOf(keyPropType),
-    selectedKeys: PropTypes.arrayOf(keyPropType),
-    onClick: PropTypes.func,
-    onDoubleClick: PropTypes.func,
-    onExpand: PropTypes.func,
-    onCheck: PropTypes.func,
-    onSelect: PropTypes.func,
-    onLoad: PropTypes.func,
-    loadData: PropTypes.func,
-    loadedKeys: PropTypes.arrayOf(keyPropType),
-    onMouseEnter: PropTypes.func,
-    onMouseLeave: PropTypes.func,
-    onRightClick: PropTypes.func,
-    onDragStart: PropTypes.func,
-    onDragEnter: PropTypes.func,
-    onDragOver: PropTypes.func,
-    onDragLeave: PropTypes.func,
-    onDragEnd: PropTypes.func,
-    onDrop: PropTypes.func,
-    filterTreeNode: PropTypes.func,
-    motion: PropTypes.object,
-    switcherIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-  };
-
   static defaultProps = {
     prefixCls: 'rc-tree',
     showLine: false,
@@ -1170,6 +1119,4 @@ class Tree extends React.Component<TreeProps, TreeState> {
   }
 }
 
-polyfill(Tree);
-
 export default Tree;
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 7c221c63..d9e318a2 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -1,8 +1,6 @@
 import * as React from 'react';
-import PropTypes from 'prop-types';
 import classNames from 'classnames';
 // @ts-ignore
-import { polyfill } from 'react-lifecycles-compat';
 import { TreeContext, TreeContextProps } from './contextTypes';
 import { getDataAndAria } from './util';
 import { IconType, Key, DataNode } from './interface';
@@ -60,36 +58,6 @@ export interface TreeNodeState {
 }
 
 class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeState> {
-  static propTypes = {
-    prefixCls: PropTypes.string,
-    className: PropTypes.string,
-    style: PropTypes.object,
-    onSelect: PropTypes.func,
-
-    // By parent
-    eventKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
-    expanded: PropTypes.bool,
-    selected: PropTypes.bool,
-    checked: PropTypes.bool,
-    loaded: PropTypes.bool,
-    loading: PropTypes.bool,
-    halfChecked: PropTypes.bool,
-    title: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-    dragOver: PropTypes.bool,
-    dragOverGapTop: PropTypes.bool,
-    dragOverGapBottom: PropTypes.bool,
-    pos: PropTypes.string,
-
-    // By user
-    isLeaf: PropTypes.bool,
-    checkable: PropTypes.bool,
-    selectable: PropTypes.bool,
-    disabled: PropTypes.bool,
-    disableCheckbox: PropTypes.bool,
-    icon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-    switcherIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
-  };
-
   public state = {
     dragNodeHighlight: false,
   };
@@ -548,8 +516,6 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
   }
 }
 
-polyfill(InternalTreeNode);
-
 const ContextTreeNode: React.FC<TreeNodeProps> = props => (
   <TreeContext.Consumer>
     {context => <InternalTreeNode {...props} context={context} />}
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index 2fce6d7e..08fdd686 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -2,7 +2,6 @@
 import React from 'react';
 import { render, mount } from 'enzyme';
 import { renderToJson } from 'enzyme-to-json';
-import PropTypes from 'prop-types';
 import { resetWarned } from 'rc-util/lib/warning';
 import Tree, { TreeNode } from '../src';
 import { InternalTreeNode } from '../src/TreeNode';
@@ -857,10 +856,6 @@ describe('Tree Props', () => {
         return isLeaf ? null : <span>{text}</span>;
       };
 
-      sfc.propTypes = {
-        isLeaf: PropTypes.bool,
-      };
-
       return sfc;
     }
     it('switcher icon', () => {

From d17cc7205ff5947ba35cb13cdb293ed76d85d03f Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Fri, 8 May 2020 11:44:43 +0800
Subject: [PATCH 093/397] 3.2.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index bb1d8588..2e43bb17 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.1.9",
+  "version": "3.2.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From bd65d2bebe0e74032793b128901262edd2c2dad1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Fri, 8 May 2020 15:43:41 +0800
Subject: [PATCH 094/397] fix: Force stop motion when motion removed (#325)

---
 src/MotionTreeNode.tsx   | 45 ++++++++++++++++++++--------------------
 tests/TreeMotion.spec.js | 16 ++++++++++++++
 2 files changed, 39 insertions(+), 22 deletions(-)

diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 255eecf1..7c191434 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -17,17 +17,14 @@ interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
   treeNodeRequiredProps: TreeNodeRequiredProps;
 }
 
-const MotionTreeNode: React.ForwardRefRenderFunction<
-  CSSMotion,
-  MotionTreeNodeProps
-> = (
+const MotionTreeNode: React.ForwardRefRenderFunction<typeof CSSMotion, MotionTreeNodeProps> = (
   {
     className,
     style,
     motion,
     motionNodes,
     motionType,
-    onMotionEnd,
+    onMotionEnd: onOriginMotionEnd,
     active,
     treeNodeRequiredProps,
     ...props
@@ -37,12 +34,30 @@ const MotionTreeNode: React.ForwardRefRenderFunction<
   const [visible, setVisible] = React.useState(true);
   const { prefixCls } = React.useContext(TreeContext);
 
+  const motionedRef = React.useRef(false);
+
+  const onMotionEnd = () => {
+    if (!motionedRef.current) {
+      onOriginMotionEnd();
+    }
+    motionedRef.current = true;
+  };
+
   React.useEffect(() => {
     if (motionNodes && motionType === 'hide' && visible) {
       setVisible(false);
     }
   }, [motionNodes]);
 
+  React.useEffect(
+    () => () => {
+      if (motionNodes) {
+        onMotionEnd();
+      }
+    },
+    [],
+  );
+
   if (motionNodes) {
     return (
       <CSSMotion
@@ -56,10 +71,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<
         {({ className: motionClassName, style: motionStyle }, motionRef) => (
           <div
             ref={motionRef}
-            className={classNames(
-              `${prefixCls}-treenode-motion`,
-              motionClassName,
-            )}
+            className={classNames(`${prefixCls}-treenode-motion`, motionClassName)}
             style={motionStyle}
           >
             {motionNodes.map((treeNode: FlattenNode) => {
@@ -70,10 +82,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<
               } = treeNode;
               delete restProps.children;
 
-              const treeNodeProps = getTreeNodeProps(
-                key,
-                treeNodeRequiredProps,
-              );
+              const treeNodeProps = getTreeNodeProps(key, treeNodeRequiredProps);
 
               return (
                 <TreeNode
@@ -92,15 +101,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<
       </CSSMotion>
     );
   }
-  return (
-    <TreeNode
-      domRef={ref}
-      className={className}
-      style={style}
-      {...props}
-      active={active}
-    />
-  );
+  return <TreeNode domRef={ref} className={className} style={style} {...props} active={active} />;
 };
 
 MotionTreeNode.displayName = 'MotionTreeNode';
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.js
index 20ff25fa..775c0969 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.js
@@ -3,6 +3,8 @@ import { mount } from 'enzyme';
 import { act } from 'react-dom/test-utils';
 import VirtualList from 'rc-virtual-list';
 import Tree, { TreeNode } from '../src';
+import MotionTreeNode from '../src/MotionTreeNode';
+import { TreeContext } from '../src/contextTypes';
 import { getMinimumRangeTransitionRange } from '../src/NodeList';
 
 describe('Tree Motion', () => {
@@ -128,4 +130,18 @@ describe('Tree Motion', () => {
     raf.mockRestore();
     jest.useRealTimers();
   });
+
+  it('MotionTreeNode should always trigger motion end', () => {
+    const onMotionEnd = jest.fn();
+    const wrapper = mount(
+      <TreeContext.Provider value={{ prefixCls: 'test' }}>
+        <MotionTreeNode motionNodes={[]} onMotionEnd={onMotionEnd} />
+      </TreeContext.Provider>,
+    );
+
+    expect(onMotionEnd).not.toHaveBeenCalled();
+
+    wrapper.unmount();
+    expect(onMotionEnd).toHaveBeenCalled();
+  });
 });

From cab6d040953d6261239415360d52cb6e31816bf1 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 8 May 2020 15:45:19 +0800
Subject: [PATCH 095/397] 3.2.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2e43bb17..0056099c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From d9078b4bba78cef4f1bfe59ee1fb1f649692d471 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Sat, 9 May 2020 11:37:44 +0800
Subject: [PATCH 096/397] chore(deps-dev): bump eslint from 6.8.0 to 7.0.0
 (#326)

Bumps [eslint](https://github.com/eslint/eslint) from 6.8.0 to 7.0.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v6.8.0...v7.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0056099c..b782ab9d 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",
     "enzyme-to-json": "^3.0.0",
-    "eslint": "^6.8.0",
+    "eslint": "^7.0.0",
     "father": "^2.25.0-alpha.1",
     "less": "^3.11.1",
     "np": "^6.0.0",

From 0997c771990803d3da69ab5d745e224a005c3e17 Mon Sep 17 00:00:00 2001
From: Thomas Stjernegaard Jeppesen <tsjeppesen@gbif.org>
Date: Tue, 12 May 2020 04:55:05 +0200
Subject: [PATCH 097/397] Handle this.dragNode === null (#327)

When dragging from another tree, this.dragNode is null  and convertNodePropsToEventData(this.dragNode) will fail.
---
 src/Tree.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 00f7b6c7..2a4737f8 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -499,7 +499,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     const dropResult = {
       event,
       node: convertNodePropsToEventData(node.props),
-      dragNode: convertNodePropsToEventData(this.dragNode.props),
+      dragNode: this.dragNode ? convertNodePropsToEventData(this.dragNode.props) : null,
       dragNodesKeys: dragNodesKeys.slice(),
       dropPosition: dropPosition + Number(posArr[posArr.length - 1]),
       dropToGap: false,

From e57f19cf08293c6c4010aa4290dca3c8fa9ac7d7 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 12 May 2020 10:56:22 +0800
Subject: [PATCH 098/397] 3.2.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b782ab9d..16c352e1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.2.1",
+  "version": "3.2.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 8fcaa82650b32de1258889ee637da0eb4068379d Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Sat, 30 May 2020 00:04:33 +0800
Subject: [PATCH 099/397] :up: upgrade rc-util to 5.x

---
 .fatherrc.js | 1 +
 package.json | 7 ++++---
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/.fatherrc.js b/.fatherrc.js
index 767a2abf..912aa0aa 100644
--- a/.fatherrc.js
+++ b/.fatherrc.js
@@ -5,4 +5,5 @@ export default {
     eslint: true,
     prettier: true,
   },
+  runtimeHelpers: true,
 };
diff --git a/package.json b/package.json
index 16c352e1..6780e396 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
     "start": "father doc dev --storybook",
     "build": "father doc build --storybook",
     "compile": "father build && lessc assets/index.less assets/index.css",
-    "prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish",
+    "prepublishOnly": "npm run compile && np --yolo --no-publish",
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js",
     "test": "father test",
@@ -68,9 +68,10 @@
     "typescript": "^3.5.2"
   },
   "dependencies": {
+    "@babel/runtime": "^7.10.1",
     "classnames": "2.x",
-    "rc-animate": "^3.0.0",
-    "rc-util": "^4.11.0",
+    "rc-animate": "^3.1.0",
+    "rc-util": "^5.0.0",
     "rc-virtual-list": "^1.1.0"
   }
 }

From 4c066e9c4a027bf582667c2637ea5181d2dbb9d5 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Sat, 30 May 2020 00:05:54 +0800
Subject: [PATCH 100/397] 3.3.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6780e396..6e6b4329 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.2.2",
+  "version": "3.3.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 039fa5f4e87dc0be4e0d70ad3603e3a83249e540 Mon Sep 17 00:00:00 2001
From: xiangfengsu <xiangfeng_su@126.com>
Date: Mon, 8 Jun 2020 09:51:22 +0800
Subject: [PATCH 101/397] =?UTF-8?q?fix:fix=20bug=20when=20using=20treeData?=
 =?UTF-8?q?=20when=20the=20parent=20node=20key=20type=20is=20nume=E2=80=A6?=
 =?UTF-8?q?=20(#335)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* fix:fix bug when using treeData when the parent node key type is numeric, the parent node cannot be expanded

* update:use set instead

Co-authored-by: 苏祥凤 <suxiangfeng@ebaochina.cn>
---
 src/util.tsx       |  8 ++++----
 tests/Tree.spec.js | 15 +++++++++++++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/util.tsx b/src/util.tsx
index 6f06d794..25892d0c 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -151,15 +151,15 @@ export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Ke
  * @param keyEntities
  */
 export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, DataEntity>) {
-  const expandedKeys = {};
+  const expandedKeys = new Set();
 
   function conductUp(key: Key) {
-    if (expandedKeys[key]) return;
+    if (expandedKeys.has(key)) return;
 
     const entity = keyEntities[key];
     if (!entity) return;
 
-    expandedKeys[key] = true;
+    expandedKeys.add(key);
 
     const { parent, node } = entity;
 
@@ -174,7 +174,7 @@ export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, Dat
     conductUp(key);
   });
 
-  return Object.keys(expandedKeys);
+  return [...expandedKeys];
 }
 
 /**
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 2f80e6ab..449503ff 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -105,6 +105,21 @@ describe('Tree Basic', () => {
       expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
     });
 
+    it('use treeData to expand the parent node when the parent node key type is numeric', () => {
+      const Demo = () => (
+          <Tree
+            defaultExpandParent
+            defaultExpandedKeys={[22]}
+            treeData={[{ key: 11, title: 11, children: [{ key: 22, title: 22 }] }]}
+          />
+        );
+
+      const wrapper = mount(<Demo />);
+
+      const switcher = wrapper.find('.rc-tree-switcher').first();
+      expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
+    });
+
     it('does not expand parent node when autoExpandParent is false', () => {
       const wrapper = mount(
         <Tree expandedKeys={['0-0-0']} defaultExpandParent={false}>

From 18ddf7c5f38da0955c5a2f9689eefb9aa65b9c29 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 8 Jun 2020 09:54:58 +0800
Subject: [PATCH 102/397] chore: Fix ts define

---
 src/util.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/util.tsx b/src/util.tsx
index 25892d0c..227a5ec6 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -150,8 +150,8 @@ export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Ke
  * @param keyList
  * @param keyEntities
  */
-export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, DataEntity>) {
-  const expandedKeys = new Set();
+export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, DataEntity>): Key[] {
+  const expandedKeys = new Set<Key>();
 
   function conductUp(key: Key) {
     if (expandedKeys.has(key)) return;

From 15bc7436282ae9808e419c08aa083e1110797d16 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 8 Jun 2020 09:55:29 +0800
Subject: [PATCH 103/397] 3.3.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6e6b4329..8979a4cd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.3.0",
+  "version": "3.3.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 8e5422b4c572ad4cd549fde73a0a62302daa4b7d Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Tue, 16 Jun 2020 11:28:51 +0800
Subject: [PATCH 104/397] feat: support rowKey on convertDataToEntities for
 table usage (#341)

* feat: support rowKey on convertDataToEntities for table usage

* refactor: convertDataToEntities with rowKey

* refactor: better perf & rename vars

* refactor: rename vars

* fix: lint error

* refactor: rename getKey related functions
---
 examples/draggable.jsx |  2 ++
 examples/dynamic.jsx   |  2 ++
 src/interface.tsx      |  2 ++
 src/utils/treeUtil.ts  | 62 ++++++++++++++++++++++++++----------------
 tests/util.js          |  3 ++
 tests/util.spec.js     | 39 ++++++++++++++++++++++++++
 6 files changed, 86 insertions(+), 24 deletions(-)

diff --git a/examples/draggable.jsx b/examples/draggable.jsx
index 7a907d3f..b933d408 100644
--- a/examples/draggable.jsx
+++ b/examples/draggable.jsx
@@ -53,6 +53,7 @@ class Demo extends React.Component {
     if (!info.dropToGap) {
       // Drop on the content
       loop(data, dropKey, item => {
+        // eslint-disable-next-line no-param-reassign
         item.children = item.children || [];
         // where to insert 示例添加到尾部，可以是随意位置
         item.children.push(dragObj);
@@ -63,6 +64,7 @@ class Demo extends React.Component {
       dropPosition === 1 // On the bottom gap
     ) {
       loop(data, dropKey, item => {
+        // eslint-disable-next-line no-param-reassign
         item.children = item.children || [];
         // where to insert 示例添加到尾部，可以是随意位置
         item.children.unshift(dragObj);
diff --git a/examples/dynamic.jsx b/examples/dynamic.jsx
index efb4b03c..26957409 100644
--- a/examples/dynamic.jsx
+++ b/examples/dynamic.jsx
@@ -26,6 +26,7 @@ function setLeaf(treeData, curKey, level) {
       if (item.children) {
         loopLeaf(item.children, l);
       } else if (l < 1) {
+        // eslint-disable-next-line no-param-reassign
         item.isLeaf = true;
       }
     });
@@ -41,6 +42,7 @@ function getNewTreeData(treeData, curKey, child, level) {
         if (item.children) {
           loop(item.children);
         } else {
+          // eslint-disable-next-line no-param-reassign
           item.children = child;
         }
       }
diff --git a/src/interface.tsx b/src/interface.tsx
index c7cd0b8f..88e597c9 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -73,3 +73,5 @@ export interface FlattenNode {
 }
 
 export type ScrollTo = (scroll: { key: Key }) => void;
+
+export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 43019844..980132c6 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -8,6 +8,7 @@ import {
   DataEntity,
   Key,
   EventDataNode,
+  GetKey,
 } from '../interface';
 import { getPosition, isTreeNode } from '../util';
 import { TreeNodeProps } from '../TreeNode';
@@ -57,10 +58,7 @@ export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
       .map(treeNode => {
         // Filter invalidate node
         if (!isTreeNode(treeNode)) {
-          warning(
-            !treeNode,
-            'Tree/TreeNode can only accept TreeNode as children.',
-          );
+          warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
           return null;
         }
 
@@ -146,7 +144,19 @@ export function traverseDataNodes(
     parentPos: string | number;
     level: number;
   }) => void,
+  externalGetKey?: GetKey<DataNode> | string,
 ) {
+  let syntheticGetKey: (node: DataNode, pos?: string) => Key;
+  if (externalGetKey) {
+    if (typeof externalGetKey === 'string') {
+      syntheticGetKey = (node: DataNode) => (node as any)[externalGetKey as string];
+    } else if (typeof externalGetKey === 'function') {
+      syntheticGetKey = (node: DataNode) => (externalGetKey as GetKey<DataNode>)(node);
+    }
+  } else {
+    syntheticGetKey = (node, pos) => getKey(node.key, pos);
+  }
+
   function processNode(
     node: DataNode,
     index?: number,
@@ -157,11 +167,12 @@ export function traverseDataNodes(
 
     // Process node if is not root
     if (node) {
+      const key: Key = syntheticGetKey(node, pos);
       const data = {
         node,
         index,
         pos,
-        key: node.key !== null ? node.key : pos,
+        key,
         parentPos: parent.node ? parent.pos : null,
         level: parent.level + 1,
       };
@@ -203,6 +214,7 @@ export function convertDataToEntities(
     processEntity?: (entity: DataEntity, wrapper: Wrapper) => void;
     onProcessFinished?: (wrapper: Wrapper) => void;
   } = {},
+  externalGetKey?: GetKey<DataNode> | string,
 ) {
   const posEntities = {};
   const keyEntities = {};
@@ -215,26 +227,30 @@ export function convertDataToEntities(
     wrapper = initWrapper(wrapper) || wrapper;
   }
 
-  traverseDataNodes(dataNodes, item => {
-    const { node, index, pos, key, parentPos, level } = item;
-    const entity: DataEntity = { node, index, key, pos, level };
+  traverseDataNodes(
+    dataNodes,
+    item => {
+      const { node, index, pos, key, parentPos, level } = item;
+      const entity: DataEntity = { node, index, key, pos, level };
 
-    const mergedKey = getKey(key, pos);
+      const mergedKey = getKey(key, pos);
 
-    posEntities[pos] = entity;
-    keyEntities[mergedKey] = entity;
+      posEntities[pos] = entity;
+      keyEntities[mergedKey] = entity;
 
-    // Fill children
-    entity.parent = posEntities[parentPos];
-    if (entity.parent) {
-      entity.parent.children = entity.parent.children || [];
-      entity.parent.children.push(entity);
-    }
+      // Fill children
+      entity.parent = posEntities[parentPos];
+      if (entity.parent) {
+        entity.parent.children = entity.parent.children || [];
+        entity.parent.children.push(entity);
+      }
 
-    if (processEntity) {
-      processEntity(entity, wrapper);
-    }
-  });
+      if (processEntity) {
+        processEntity(entity, wrapper);
+      }
+    },
+    externalGetKey,
+  );
 
   if (onProcessFinished) {
     onProcessFinished(wrapper);
@@ -293,9 +309,7 @@ export function getTreeNodeProps(
   return treeNodeProps;
 }
 
-export function convertNodePropsToEventData(
-  props: TreeNodeProps,
-): EventDataNode {
+export function convertNodePropsToEventData(props: TreeNodeProps): EventDataNode {
   const {
     data,
     expanded,
diff --git a/tests/util.js b/tests/util.js
index 2b9239d8..a06da17d 100644
--- a/tests/util.js
+++ b/tests/util.js
@@ -27,9 +27,11 @@ export function spyConsole() {
     'Warning: `children` of Tree is deprecated. Please use `treeData` instead.',
   ];
 
+  // eslint-disable-next-line no-console
   const originConsoleErr = console.error;
 
   beforeAll(() => {
+    // eslint-disable-next-line no-console
     console.error = jest.fn().mockImplementation((...args) => {
       if (errorList.some(tmpl => args[0].includes(tmpl))) {
         return;
@@ -40,6 +42,7 @@ export function spyConsole() {
   });
 
   afterAll(() => {
+    // eslint-disable-next-line no-console
     console.error = originConsoleErr;
   });
 }
diff --git a/tests/util.spec.js b/tests/util.spec.js
index 040b7267..3378e0ba 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -117,6 +117,44 @@ describe('Util', () => {
     expect(Object.keys(entities.keyEntities).sort()).toEqual(['0', '1', 'parent']);
   });
 
+  it('convertDataToEntities with string externalGetKey', () => {
+    const entities = convertDataToEntities(
+      [
+        {
+          key: 'parent',
+          notKey: 'let it be',
+          children: [{ key: 0, notKey: 'penny lane' }, { key: 1, notKey: 'please please me' }],
+        },
+      ],
+      undefined,
+      'notKey',
+    );
+    expect(Object.keys(entities.keyEntities).sort()).toEqual([
+      'let it be',
+      'penny lane',
+      'please please me',
+    ]);
+  });
+
+  it('convertDataToEntities with function externalGetKey', () => {
+    const entities = convertDataToEntities(
+      [
+        {
+          key: 'parent',
+          notKey: 'let it be',
+          children: [{ key: 0, notKey: 'penny lane' }, { key: 1, notKey: 'please please me' }],
+        },
+      ],
+      undefined,
+      entity => entity.notKey,
+    );
+    expect(Object.keys(entities.keyEntities).sort()).toEqual([
+      'let it be',
+      'penny lane',
+      'please please me',
+    ]);
+  });
+
   it('convertTreeToEntities with additional handler', () => {
     const onProcessFinished = jest.fn();
 
@@ -134,6 +172,7 @@ describe('Util', () => {
           valueEntities: {},
         }),
         processEntity: (entity, wrapper) => {
+          // eslint-disable-next-line no-param-reassign
           wrapper.valueEntities[entity.node.value] = entity;
         },
         onProcessFinished,

From ffaf5505b9c305a14ed3cf592e7c68eba97a5163 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 16 Jun 2020 11:30:30 +0800
Subject: [PATCH 105/397] 3.4.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8979a4cd..cc8a436d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.3.1",
+  "version": "3.4.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From dc8954f608a5bb892a0d255c2f2306dbd3f54dc1 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Tue, 16 Jun 2020 16:36:51 +0800
Subject: [PATCH 106/397] feat: support custom checkbox disabled on
 util.conductCheck (#343)

---
 src/interface.tsx        |  2 ++
 src/utils/conductUtil.ts | 42 ++++++++++++++++-------
 tests/util.spec.js       | 73 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 104 insertions(+), 13 deletions(-)

diff --git a/src/interface.tsx b/src/interface.tsx
index 88e597c9..c8f72049 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -75,3 +75,5 @@ export interface FlattenNode {
 export type ScrollTo = (scroll: { key: Key }) => void;
 
 export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
+
+export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;
diff --git a/src/utils/conductUtil.ts b/src/utils/conductUtil.ts
index 766357bd..8a523954 100644
--- a/src/utils/conductUtil.ts
+++ b/src/utils/conductUtil.ts
@@ -1,5 +1,5 @@
 import warning from 'rc-util/lib/warning';
-import { Key, DataEntity, DataNode } from '../interface';
+import { Key, DataEntity, DataNode, GetCheckDisabled } from '../interface';
 
 interface ConductReturnType {
   checkedKeys: Key[];
@@ -26,6 +26,7 @@ function fillConductCheck(
   keys: Set<Key>,
   levelEntities: Map<number, Set<DataEntity>>,
   maxLevel: number,
+  syntheticGetCheckDisabled: GetCheckDisabled<DataNode>,
 ): ConductReturnType {
   const checkedKeys = new Set<Key>(keys);
   const halfCheckedKeys = new Set<Key>();
@@ -36,9 +37,9 @@ function fillConductCheck(
     entities.forEach(entity => {
       const { key, node, children = [] } = entity;
 
-      if (checkedKeys.has(key) && !isCheckDisabled(node)) {
+      if (checkedKeys.has(key) && !syntheticGetCheckDisabled(node)) {
         children
-          .filter(childEntity => !isCheckDisabled(childEntity.node))
+          .filter(childEntity => !syntheticGetCheckDisabled(childEntity.node))
           .forEach(childEntity => {
             checkedKeys.add(childEntity.key);
           });
@@ -54,12 +55,12 @@ function fillConductCheck(
       const { parent, node } = entity;
 
       // Skip if no need to check
-      if (isCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
+      if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
         return;
       }
 
       // Skip if parent is disabled
-      if (isCheckDisabled(entity.parent.node)) {
+      if (syntheticGetCheckDisabled(entity.parent.node)) {
         visitedKeys.add(parent.key);
         return;
       }
@@ -68,7 +69,7 @@ function fillConductCheck(
       let partialChecked = false;
 
       (parent.children || [])
-        .filter(childEntity => !isCheckDisabled(childEntity.node))
+        .filter(childEntity => !syntheticGetCheckDisabled(childEntity.node))
         .forEach(({ key }) => {
           const checked = checkedKeys.has(key);
           if (allChecked && !checked) {
@@ -102,6 +103,7 @@ function cleanConductCheck(
   halfKeys: Key[],
   levelEntities: Map<number, Set<DataEntity>>,
   maxLevel: number,
+  syntheticGetCheckDisabled: GetCheckDisabled<DataNode>,
 ): ConductReturnType {
   const checkedKeys = new Set<Key>(keys);
   let halfCheckedKeys = new Set<Key>(halfKeys);
@@ -112,9 +114,9 @@ function cleanConductCheck(
     entities.forEach(entity => {
       const { key, node, children = [] } = entity;
 
-      if (!checkedKeys.has(key) && !halfCheckedKeys.has(key) && !isCheckDisabled(node)) {
+      if (!checkedKeys.has(key) && !halfCheckedKeys.has(key) && !syntheticGetCheckDisabled(node)) {
         children
-          .filter(childEntity => !isCheckDisabled(childEntity.node))
+          .filter(childEntity => !syntheticGetCheckDisabled(childEntity.node))
           .forEach(childEntity => {
             checkedKeys.delete(childEntity.key);
           });
@@ -132,12 +134,12 @@ function cleanConductCheck(
       const { parent, node } = entity;
 
       // Skip if no need to check
-      if (isCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
+      if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) {
         return;
       }
 
       // Skip if parent is disabled
-      if (isCheckDisabled(entity.parent.node)) {
+      if (syntheticGetCheckDisabled(entity.parent.node)) {
         visitedKeys.add(parent.key);
         return;
       }
@@ -146,7 +148,7 @@ function cleanConductCheck(
       let partialChecked = false;
 
       (parent.children || [])
-        .filter(childEntity => !isCheckDisabled(childEntity.node))
+        .filter(childEntity => !syntheticGetCheckDisabled(childEntity.node))
         .forEach(({ key }) => {
           const checked = checkedKeys.has(key);
           if (allChecked && !checked) {
@@ -184,9 +186,17 @@ export function conductCheck(
   keyList: Key[],
   checked: true | { checked: false; halfCheckedKeys: Key[] },
   keyEntities: Record<Key, DataEntity>,
+  getCheckDisabled?: GetCheckDisabled<DataNode>,
 ): ConductReturnType {
   const warningMissKeys: Key[] = [];
 
+  let syntheticGetCheckDisabled: GetCheckDisabled<DataNode>;
+  if (getCheckDisabled) {
+    syntheticGetCheckDisabled = getCheckDisabled;
+  } else {
+    syntheticGetCheckDisabled = isCheckDisabled;
+  }
+
   // We only handle exist keys
   const keys = new Set<Key>(
     keyList.filter(key => {
@@ -227,9 +237,15 @@ export function conductCheck(
 
   let result: ConductReturnType;
   if (checked === true) {
-    result = fillConductCheck(keys, levelEntities, maxLevel);
+    result = fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled);
   } else {
-    result = cleanConductCheck(keys, checked.halfCheckedKeys, levelEntities, maxLevel);
+    result = cleanConductCheck(
+      keys,
+      checked.halfCheckedKeys,
+      levelEntities,
+      maxLevel,
+      syntheticGetCheckDisabled,
+    );
   }
 
   return result;
diff --git a/tests/util.spec.js b/tests/util.spec.js
index 3378e0ba..adbd490c 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -323,6 +323,79 @@ describe('Util', () => {
         expect(result2.checkedKeys.sort()).toEqual(['war', 'are', 'not', 'it'].sort());
         expect(result2.halfCheckedKeys.sort()).toEqual([].sort());
       });
+
+      const genNonDisabledTree = props => (
+        <Tree {...props}>
+          <TreeNode key="war">
+            <TreeNode key="are" />
+            <TreeNode key="not">
+              <TreeNode key="it" />
+              <TreeNode key="used">
+                <TreeNode key="to" />
+                <TreeNode key="be" />
+              </TreeNode>
+            </TreeNode>
+          </TreeNode>
+        </Tree>
+      );
+
+      it('check with custom checkbox disabled', () => {
+        const tree = genNonDisabledTree();
+        const { keyEntities } = convertDataToEntities(
+          convertTreeToData(tree.props.children),
+          undefined,
+          undefined,
+        );
+
+        const getCheckDisabled = data => data.key === 'used';
+
+        const result1 = conductCheck(['not'], true, keyEntities, getCheckDisabled);
+        expect(result1.checkedKeys.sort()).toEqual(['not', 'it'].sort());
+        expect(result1.halfCheckedKeys.sort()).toEqual(['war'].sort());
+
+        const result2 = conductCheck(['to', 'be'], true, keyEntities, getCheckDisabled);
+        expect(result2.checkedKeys.sort()).toEqual(['to', 'be'].sort());
+        expect(result2.halfCheckedKeys.sort()).toEqual([].sort());
+      });
+
+      it('uncheck with custom checkbox disabled', () => {
+        const tree = genNonDisabledTree();
+        const { keyEntities } = convertDataToEntities(
+          convertTreeToData(tree.props.children),
+          undefined,
+          undefined,
+        );
+
+        const getCheckDisabled = data => data.key === 'used';
+
+        // First, we check all
+        const allCheckedKeys = conductCheck(
+          ['war', 'to', 'be'],
+          true,
+          keyEntities,
+          getCheckDisabled,
+        ).checkedKeys;
+        expect(allCheckedKeys.length).toEqual(6);
+
+        // Then uncheck one of then
+        const result1 = conductCheck(
+          allCheckedKeys.filter(key => key !== 'not'),
+          { checked: false, halfCheckedKeys: [] },
+          keyEntities,
+          getCheckDisabled,
+        );
+        expect(result1.checkedKeys.sort()).toEqual(['are', 'to', 'be'].sort());
+        expect(result1.halfCheckedKeys.sort()).toEqual(['war'].sort());
+
+        const result2 = conductCheck(
+          allCheckedKeys.filter(key => key !== 'to' && key !== 'be'),
+          { checked: false, halfCheckedKeys: [] },
+          keyEntities,
+          getCheckDisabled,
+        );
+        expect(result2.checkedKeys.sort()).toEqual(['war', 'are', 'not', 'it'].sort());
+        expect(result2.halfCheckedKeys.sort()).toEqual([].sort());
+      });
     });
   });
 

From 8d3e75cf67d174c104a7a71fe3270d413cf02d69 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 16 Jun 2020 16:38:18 +0800
Subject: [PATCH 107/397] 3.5.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index cc8a436d..424ffed3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.4.0",
+  "version": "3.5.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 42000109d3c177633f486cf43cd3a694ee0ef80e Mon Sep 17 00:00:00 2001
From: zoomdong <1344492820@qq.com>
Date: Fri, 19 Jun 2020 11:07:13 +0800
Subject: [PATCH 108/397] fix: pre-commit hook (#345)

* fix: pre-commit hook

* adjust
---
 package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package.json b/package.json
index 424ffed3..d8c093c6 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
     "@types/react": "^16.8.19",
     "@types/react-dom": "^16.8.4",
     "@types/warning": "^3.0.0",
+    "@typescript-eslint/parser": "^3.3.0",
     "css-animation": "^1.2.0",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",

From f8ea91c091e85480987a355e9223ab4806efbb18 Mon Sep 17 00:00:00 2001
From: zoomdong <1344492820@qq.com>
Date: Thu, 25 Jun 2020 11:54:17 +0800
Subject: [PATCH 109/397] feat: add last className for Treeleaf (#346)

* feat: add last className for Treeleaf

* feat: update snapshot
---
 src/TreeNode.tsx                              |  3 +-
 tests/__snapshots__/Tree.spec.js.snap         | 24 +++---
 .../__snapshots__/TreeNodeProps.spec.js.snap  | 36 ++++-----
 tests/__snapshots__/TreeProps.spec.js.snap    | 74 +++++++++----------
 4 files changed, 69 insertions(+), 68 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index d9e318a2..0e5a4fc8 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -480,7 +480,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const disabled = this.isDisabled();
     const dataOrAriaAttributeProps = getDataAndAria(otherProps);
     const { level } = keyEntities[eventKey] || {};
-
+    const isEndNode = isEnd[isEnd.length - 1];
     return (
       <div
         ref={domRef}
@@ -492,6 +492,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           [`${prefixCls}-treenode-selected`]: selected,
           [`${prefixCls}-treenode-loading`]: loading,
           [`${prefixCls}-treenode-active`]: active,
+          [`${prefixCls}-treenode-leaf-last`]: isEndNode,
 
           'drag-over': !disabled && dragOver,
           'drag-over-gap-top': !disabled && dragOverGapTop,
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index d5d6fb62..b4c76ccc 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -22,7 +22,7 @@ exports[`Tree Basic check basic render 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -76,7 +76,7 @@ exports[`Tree Basic check basic render 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -134,7 +134,7 @@ exports[`Tree Basic check check after data ready works 1`] = `
         style="display: flex; flex-direction: column;"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-checkbox-checked"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -157,7 +157,7 @@ exports[`Tree Basic check check after data ready works 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -215,7 +215,7 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
         style="display: flex; flex-direction: column;"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -285,7 +285,7 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -352,7 +352,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -400,7 +400,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -455,7 +455,7 @@ exports[`Tree Basic renders correctly 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="spe rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="spe rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -543,7 +543,7 @@ exports[`Tree Basic renders correctly 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -577,7 +577,7 @@ exports[`Tree Basic renders correctly 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -635,7 +635,7 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_close"
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index bbd5a326..1b2aac2b 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -70,7 +70,7 @@ exports[`TreeNode Props className 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -101,7 +101,7 @@ exports[`TreeNode Props className 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -129,7 +129,7 @@ exports[`TreeNode Props className 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -176,7 +176,7 @@ exports[`TreeNode Props customize icon component 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -227,7 +227,7 @@ exports[`TreeNode Props customize icon element 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -278,7 +278,7 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -373,7 +373,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
         </div>
         <div
           aria-label="0-0-0-0"
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -405,7 +405,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
         </div>
         <div
           aria-label="0-0-1"
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -434,7 +434,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
         </div>
         <div
           aria-label="0-1"
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -531,7 +531,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           data-test="0-0-0-0"
         >
           <span
@@ -563,7 +563,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           data-test="0-0-1"
         >
           <span
@@ -592,7 +592,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           data-test="0-1"
         >
           <span
@@ -640,7 +640,7 @@ exports[`TreeNode Props isLeaf 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode"
+          class="rc-tree-treenode rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -657,7 +657,7 @@ exports[`TreeNode Props isLeaf 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode"
+          class="rc-tree-treenode rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -709,7 +709,7 @@ exports[`TreeNode Props isLeaf 2`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode"
+          class="rc-tree-treenode rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -729,7 +729,7 @@ exports[`TreeNode Props isLeaf 2`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode"
+          class="rc-tree-treenode rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -784,7 +784,7 @@ exports[`TreeNode Props isLeaf 3`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -804,7 +804,7 @@ exports[`TreeNode Props isLeaf 3`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index b6d8ec19..17d5a410 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -22,7 +22,7 @@ exports[`Tree Props checkStrictly 1`] = `
         style="display: flex; flex-direction: column;"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -45,7 +45,7 @@ exports[`Tree Props checkStrictly 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -103,7 +103,7 @@ exports[`Tree Props checkable default 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -126,7 +126,7 @@ exports[`Tree Props checkable default 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -184,7 +184,7 @@ exports[`Tree Props checkable without selectable 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -207,7 +207,7 @@ exports[`Tree Props checkable without selectable 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -285,7 +285,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -337,7 +337,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -492,7 +492,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -524,7 +524,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -629,7 +629,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -649,7 +649,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -705,7 +705,7 @@ exports[`Tree Props disabled basic 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -753,7 +753,7 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -800,7 +800,7 @@ exports[`Tree Props icon 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -824,7 +824,7 @@ exports[`Tree Props icon 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -883,7 +883,7 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
         style="display: flex; flex-direction: column;"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -906,7 +906,7 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -964,7 +964,7 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
         style="display: flex; flex-direction: column;"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -987,7 +987,7 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -1045,7 +1045,7 @@ exports[`Tree Props loadData basic 1`] = `
         style="display: flex; flex-direction: column;"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -1062,7 +1062,7 @@ exports[`Tree Props loadData basic 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -1114,7 +1114,7 @@ exports[`Tree Props multiple 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -1134,7 +1134,7 @@ exports[`Tree Props multiple 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -1241,7 +1241,7 @@ exports[`Tree Props selectable with selectable 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -1261,7 +1261,7 @@ exports[`Tree Props selectable with selectable 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -1316,7 +1316,7 @@ exports[`Tree Props selectable without selectable 1`] = `
         style="display:flex;flex-direction:column"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -1336,7 +1336,7 @@ exports[`Tree Props selectable without selectable 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -1411,7 +1411,7 @@ exports[`Tree Props showIcon 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -1475,7 +1475,7 @@ exports[`Tree Props showIcon 2`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -1575,7 +1575,7 @@ exports[`Tree Props showIcon 3`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -1606,7 +1606,7 @@ exports[`Tree Props showIcon 3`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -1634,7 +1634,7 @@ exports[`Tree Props showIcon 3`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"
@@ -1727,7 +1727,7 @@ exports[`Tree Props treeData 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher_open"
@@ -1834,7 +1834,7 @@ exports[`Tree Props treeData 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -1865,7 +1865,7 @@ exports[`Tree Props treeData 1`] = `
           </span>
         </div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             aria-hidden="true"
@@ -1920,7 +1920,7 @@ exports[`Tree Props treeData 2`] = `
         style="display: flex; flex-direction: column;"
       >
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
         >
           <span
             class="rc-tree-switcher rc-tree-switcher-noop"

From be334064c8a328b79211583f86b7f409765d42fb Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Thu, 25 Jun 2020 11:55:05 +0800
Subject: [PATCH 110/397] 3.6.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d8c093c6..9b5b3505 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.5.0",
+  "version": "3.6.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From eb86a6d7af076a8fd3a8a185757f925b341c8b6d Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Tue, 7 Jul 2020 10:45:53 +0800
Subject: [PATCH 111/397] chore(deps-dev): bump @types/jest from 25.2.3 to
 26.0.4 (#348)

Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 25.2.3 to 26.0.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 9b5b3505..2b2975a0 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
     "react-dom": "*"
   },
   "devDependencies": {
-    "@types/jest": "^25.2.1",
+    "@types/jest": "^26.0.4",
     "@types/react": "^16.8.19",
     "@types/react-dom": "^16.8.4",
     "@types/warning": "^3.0.0",

From 42b1f6c33a71c0878db1409cbe508ddb36354eee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 14 Jul 2020 22:49:04 +0800
Subject: [PATCH 112/397] feat: Support titleRender (#351)

---
 src/Tree.tsx            |  4 ++++
 src/TreeNode.tsx        | 17 +++++++++++------
 src/contextTypes.ts     |  3 ++-
 tests/TreeProps.spec.js | 28 ++++++++++++++++++++++++++++
 4 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 2a4737f8..4670b741 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -76,6 +76,7 @@ export interface TreeProps {
   checkedKeys?: Key[] | { checked: Key[]; halfChecked: Key[] };
   defaultSelectedKeys?: Key[];
   selectedKeys?: Key[];
+  titleRender?: (node: DataNode) => React.ReactNode;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
@@ -1043,6 +1044,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       height,
       itemHeight,
       virtual,
+      titleRender,
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
 
@@ -1063,6 +1065,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
           loadData,
           filterTreeNode,
 
+          titleRender,
+
           onNodeClick: this.onNodeClick,
           onNodeDoubleClick: this.onNodeDoubleClick,
           onNodeExpand: this.onNodeExpand,
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 0e5a4fc8..db687720 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -398,7 +398,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const { dragNodeHighlight } = this.state;
     const { title, selected, icon, loading, data } = this.props;
     const {
-      context: { prefixCls, showIcon, icon: treeIcon, draggable, loadData },
+      context: { prefixCls, showIcon, icon: treeIcon, draggable, loadData, titleRender },
     } = this.props;
     const disabled = this.isDisabled();
 
@@ -422,11 +422,16 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     }
 
     // Title
-    const $title = (
-      <span className={`${prefixCls}-title`}>
-        {typeof title === 'function' ? title(data) : title}
-      </span>
-    );
+    let titleNode: React.ReactNode;
+    if (typeof title === 'function') {
+      titleNode = title(data);
+    } else if (titleRender) {
+      titleNode = titleRender(data);
+    } else {
+      titleNode = title;
+    }
+
+    const $title = <span className={`${prefixCls}-title`}>{titleNode}</span>;
 
     return (
       <span
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 312b45ab..7f6734ce 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -3,7 +3,7 @@
  * When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
  */
 import * as React from 'react';
-import { IconType, Key, DataEntity, EventDataNode, NodeInstance } from './interface';
+import { IconType, Key, DataEntity, EventDataNode, NodeInstance, DataNode } from './interface';
 
 type NodeMouseEventHandler = (e: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => void;
 type NodeDragEventHandler = (e: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => void;
@@ -22,6 +22,7 @@ export interface TreeContextProps {
 
   loadData: (treeNode: EventDataNode) => Promise<void>;
   filterTreeNode: (treeNode: EventDataNode) => boolean;
+  titleRender?: (node: DataNode) => React.ReactNode;
 
   onNodeClick: NodeMouseEventHandler;
   onNodeDoubleClick: NodeMouseEventHandler;
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index 08fdd686..cd5572dd 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -895,4 +895,32 @@ describe('Tree Props', () => {
     const wrapper = mount(<Tree style={style} />);
     expect(wrapper.props().style).toEqual(style);
   });
+
+  it('titleRender', () => {
+    const wrapper = mount(
+      <Tree
+        defaultExpandAll
+        titleRender={({ value }) => <span className="bamboo-span">{value}</span>}
+        treeData={[
+          { title: ({ value }) => <span className="light-span">{value}</span>, value: 'light' },
+          { value: 'bamboo' },
+        ]}
+      />,
+    );
+
+    expect(
+      wrapper
+        .find('.rc-tree-title')
+        .first()
+        .find('.light-span')
+        .text(),
+    ).toEqual('light');
+    expect(
+      wrapper
+        .find('.rc-tree-title')
+        .last()
+        .find('.bamboo-span')
+        .text(),
+    ).toEqual('bamboo');
+  });
 });

From f10259e79089046dcd73666c93a7b73032d552e1 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 14 Jul 2020 22:50:46 +0800
Subject: [PATCH 113/397] 3.7.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2b2975a0..ea6fda48 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.6.0",
+  "version": "3.7.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 890c82d03af67ad52fda2381030d65f8f5a47879 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Wed, 22 Jul 2020 17:03:08 +0800
Subject: [PATCH 114/397] support treeNodeLabelProp (#352)

* support treeNodeLabelProp

* adjust desc
---
 README.md                      |  1 +
 examples/treeNodeLabelProp.tsx | 33 +++++++++++++++++++++++++++++++++
 src/NodeList.tsx               |  5 +++++
 src/Tree.tsx                   |  3 +++
 tests/TreeProps.spec.js        | 17 +++++++++++++++++
 5 files changed, 59 insertions(+)
 create mode 100644 examples/treeNodeLabelProp.tsx

diff --git a/README.md b/README.md
index 5da49ae2..33d369a9 100644
--- a/README.md
+++ b/README.md
@@ -79,6 +79,7 @@ see examples
 | showIcon | whether show icon | bool | true |
 | showLine | whether show line | bool | false |
 | treeData | treeNodes data Array, if set it then you need not to construct children TreeNode. (value should be unique across the whole array) | array<{key,title,children, [disabled, selectable]}> | - |
+| treeNodeLabelProp | Set display title node of treeNode | string | title |
 | onCheck | click the treeNode/checkbox to fire | function(checkedKeys, e:{checked: bool, checkedNodes, node, event, nativeEvent}) | - |
 | onExpand | fire on treeNode expand or not | function(expandedKeys, {expanded: bool, node, nativeEvent}) | - |
 | onDragEnd | it execs when fire the tree's dragend event | function({event,node}) | - |
diff --git a/examples/treeNodeLabelProp.tsx b/examples/treeNodeLabelProp.tsx
new file mode 100644
index 00000000..4bfb05a0
--- /dev/null
+++ b/examples/treeNodeLabelProp.tsx
@@ -0,0 +1,33 @@
+/* eslint no-console:0 */
+/* eslint no-alert:0 */
+import React from 'react';
+import Tree from '../src';
+import '../assets/index.less';
+
+const Demo = () => (
+  <div>
+    <h2>Customize icon with element</h2>
+    <Tree
+      treeNodeLabelProp="replacedTitle"
+      defaultExpandAll
+      treeData={
+        [
+          {
+            title: 'parent',
+            replacedTitle: 'real-parent',
+            key: 1,
+            children: [
+              {
+                title: 'children',
+                replacedTitle: 'real-children',
+                key: 2,
+              },
+            ],
+          },
+        ] as any
+      }
+    />
+  </div>
+);
+
+export default Demo;
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index d94c8b91..0387eac3 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -75,6 +75,8 @@ interface NodeListProps {
   dragOverNodeKey: Key;
   dropPosition: number;
 
+  treeNodeLabelProp: string;
+
   // Virtual list
   height: number;
   itemHeight: number;
@@ -153,6 +155,8 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
     focused,
     tabIndex,
 
+    treeNodeLabelProp,
+
     onKeyDown,
     onFocus,
     onBlur,
@@ -312,6 +316,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
           return (
             <MotionTreeNode
               {...restProps}
+              title={restProps[treeNodeLabelProp]}
               {...treeNodeProps}
               active={activeItem && key === activeItem.data.key}
               pos={pos}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 4670b741..a4cd7b90 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -77,6 +77,7 @@ export interface TreeProps {
   defaultSelectedKeys?: Key[];
   selectedKeys?: Key[];
   titleRender?: (node: DataNode) => React.ReactNode;
+  treeNodeLabelProp?: string;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
@@ -1045,6 +1046,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       itemHeight,
       virtual,
       titleRender,
+      treeNodeLabelProp = 'title',
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
 
@@ -1114,6 +1116,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
             onActiveChange={this.onActiveChange}
             onListChangeStart={this.onListChangeStart}
             onListChangeEnd={this.onListChangeEnd}
+            treeNodeLabelProp={treeNodeLabelProp}
             {...this.getTreeNodeRequiredProps()}
             {...domProps}
           />
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index cd5572dd..c93c6cb4 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -923,4 +923,21 @@ describe('Tree Props', () => {
         .text(),
     ).toEqual('bamboo');
   });
+
+  it('treeNodeLabelProp', () => {
+    const wrapper = mount(
+      <Tree
+        treeNodeLabelProp="replacedTitle"
+        defaultExpandAll
+        treeData={[
+          {
+            replacedTitle: 'real-parent',
+            key: 1,
+          },
+        ]}
+      />,
+    );
+
+    expect(wrapper.find('.rc-tree-title').text()).toEqual('real-parent');
+  });
 });

From 94a26759e525ea51eb3b95b5c5c645a55ed0a83e Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 22 Jul 2020 17:04:53 +0800
Subject: [PATCH 115/397] 3.8.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index ea6fda48..fc55bda2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.7.0",
+  "version": "3.8.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From b7c58a0b97ba5497981f4e50d6c4c11b6b97f12d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Wed, 22 Jul 2020 17:20:22 +0800
Subject: [PATCH 116/397] Revert "support treeNodeLabelProp (#352)" (#353)

This reverts commit 890c82d03af67ad52fda2381030d65f8f5a47879.
---
 README.md                      |  1 -
 examples/treeNodeLabelProp.tsx | 33 ---------------------------------
 src/NodeList.tsx               |  5 -----
 src/Tree.tsx                   |  3 ---
 tests/TreeProps.spec.js        | 17 -----------------
 5 files changed, 59 deletions(-)
 delete mode 100644 examples/treeNodeLabelProp.tsx

diff --git a/README.md b/README.md
index 33d369a9..5da49ae2 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,6 @@ see examples
 | showIcon | whether show icon | bool | true |
 | showLine | whether show line | bool | false |
 | treeData | treeNodes data Array, if set it then you need not to construct children TreeNode. (value should be unique across the whole array) | array<{key,title,children, [disabled, selectable]}> | - |
-| treeNodeLabelProp | Set display title node of treeNode | string | title |
 | onCheck | click the treeNode/checkbox to fire | function(checkedKeys, e:{checked: bool, checkedNodes, node, event, nativeEvent}) | - |
 | onExpand | fire on treeNode expand or not | function(expandedKeys, {expanded: bool, node, nativeEvent}) | - |
 | onDragEnd | it execs when fire the tree's dragend event | function({event,node}) | - |
diff --git a/examples/treeNodeLabelProp.tsx b/examples/treeNodeLabelProp.tsx
deleted file mode 100644
index 4bfb05a0..00000000
--- a/examples/treeNodeLabelProp.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-/* eslint no-console:0 */
-/* eslint no-alert:0 */
-import React from 'react';
-import Tree from '../src';
-import '../assets/index.less';
-
-const Demo = () => (
-  <div>
-    <h2>Customize icon with element</h2>
-    <Tree
-      treeNodeLabelProp="replacedTitle"
-      defaultExpandAll
-      treeData={
-        [
-          {
-            title: 'parent',
-            replacedTitle: 'real-parent',
-            key: 1,
-            children: [
-              {
-                title: 'children',
-                replacedTitle: 'real-children',
-                key: 2,
-              },
-            ],
-          },
-        ] as any
-      }
-    />
-  </div>
-);
-
-export default Demo;
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 0387eac3..d94c8b91 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -75,8 +75,6 @@ interface NodeListProps {
   dragOverNodeKey: Key;
   dropPosition: number;
 
-  treeNodeLabelProp: string;
-
   // Virtual list
   height: number;
   itemHeight: number;
@@ -155,8 +153,6 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
     focused,
     tabIndex,
 
-    treeNodeLabelProp,
-
     onKeyDown,
     onFocus,
     onBlur,
@@ -316,7 +312,6 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
           return (
             <MotionTreeNode
               {...restProps}
-              title={restProps[treeNodeLabelProp]}
               {...treeNodeProps}
               active={activeItem && key === activeItem.data.key}
               pos={pos}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index a4cd7b90..4670b741 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -77,7 +77,6 @@ export interface TreeProps {
   defaultSelectedKeys?: Key[];
   selectedKeys?: Key[];
   titleRender?: (node: DataNode) => React.ReactNode;
-  treeNodeLabelProp?: string;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
@@ -1046,7 +1045,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
       itemHeight,
       virtual,
       titleRender,
-      treeNodeLabelProp = 'title',
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
 
@@ -1116,7 +1114,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
             onActiveChange={this.onActiveChange}
             onListChangeStart={this.onListChangeStart}
             onListChangeEnd={this.onListChangeEnd}
-            treeNodeLabelProp={treeNodeLabelProp}
             {...this.getTreeNodeRequiredProps()}
             {...domProps}
           />
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index c93c6cb4..cd5572dd 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -923,21 +923,4 @@ describe('Tree Props', () => {
         .text(),
     ).toEqual('bamboo');
   });
-
-  it('treeNodeLabelProp', () => {
-    const wrapper = mount(
-      <Tree
-        treeNodeLabelProp="replacedTitle"
-        defaultExpandAll
-        treeData={[
-          {
-            replacedTitle: 'real-parent',
-            key: 1,
-          },
-        ]}
-      />,
-    );
-
-    expect(wrapper.find('.rc-tree-title').text()).toEqual('real-parent');
-  });
 });

From f5817340bb82caad44b05eca49f8b37810e70741 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 22 Jul 2020 17:20:55 +0800
Subject: [PATCH 117/397] 3.8.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index fc55bda2..1998add9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.8.0",
+  "version": "3.8.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 60e34ea761bc17c51ac46d8dcf1672399a7b9c65 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Thu, 23 Jul 2020 14:18:08 +0800
Subject: [PATCH 118/397] fix: Not calculate for scroll when virtual is false
 (#354)

---
 src/NodeList.tsx | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index d94c8b91..fd7c163c 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -94,10 +94,11 @@ interface NodeListProps {
  */
 export function getMinimumRangeTransitionRange(
   list: FlattenNode[],
+  virtual: boolean,
   height: number,
   itemHeight: number,
 ) {
-  if (!height) {
+  if (!virtual || !height) {
     return list;
   }
 
@@ -203,6 +204,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         if (motion) setDisableVirtual(true);
         const rangeNodes = getMinimumRangeTransitionRange(
           getExpandRange(prevData, data, diffExpanded.key),
+          virtual,
           height,
           itemHeight,
         );
@@ -219,6 +221,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         if (motion) setDisableVirtual(true);
         const rangeNodes = getMinimumRangeTransitionRange(
           getExpandRange(data, prevData, diffExpanded.key),
+          virtual,
           height,
           itemHeight,
         );

From 2bf9b07be2caab59c8d83687003def71e04d4177 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 23 Jul 2020 14:19:46 +0800
Subject: [PATCH 119/397] 3.8.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1998add9..932b9f18 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.8.1",
+  "version": "3.8.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 9c53a958841cdc343da21a4156856e29426581c2 Mon Sep 17 00:00:00 2001
From: "Ivan.Yakovlev" <ivan.yakovlev@pochtavip.com>
Date: Mon, 3 Aug 2020 13:10:10 +0300
Subject: [PATCH 120/397] feat : fix params and handler types (#356)

---
 src/contextTypes.ts | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 7f6734ce..62e3d302 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -5,8 +5,23 @@
 import * as React from 'react';
 import { IconType, Key, DataEntity, EventDataNode, NodeInstance, DataNode } from './interface';
 
-type NodeMouseEventHandler = (e: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => void;
-type NodeDragEventHandler = (e: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => void;
+export type NodeMouseEventParams<T = HTMLSpanElement> = {
+  event: React.MouseEvent<T>;
+  node: EventDataNode;
+};
+export type NodeDragEventParams<T = HTMLSpanElement> = {
+  event: React.MouseEvent<T>;
+  node: EventDataNode;
+};
+
+export type NodeMouseEventHandler<T = HTMLSpanElement> = (
+  e: React.MouseEvent<T>,
+  node: EventDataNode,
+) => void;
+export type NodeDragEventHandler<T = HTMLDivElement> = (
+  e: React.MouseEvent<T>,
+  node: NodeInstance,
+) => void;
 
 export interface TreeContextProps {
   prefixCls: string;
@@ -29,7 +44,7 @@ export interface TreeContextProps {
   onNodeExpand: NodeMouseEventHandler;
   onNodeSelect: NodeMouseEventHandler;
   onNodeCheck: (
-    e: React.MouseEvent<HTMLDivElement>,
+    e: React.MouseEvent<HTMLSpanElement>,
     treeNode: EventDataNode,
     checked: boolean,
   ) => void;

From 0d7e319e4bdbe786ae915541fa6de5b8e9ce6a16 Mon Sep 17 00:00:00 2001
From: "Ivan.Yakovlev" <ivan.yakovlev@pochtavip.com>
Date: Mon, 3 Aug 2020 13:12:44 +0300
Subject: [PATCH 121/397] feat: types enhanced (#356)

---
 src/Tree.tsx | 72 +++++++++++++++++++++++++++-------------------------
 1 file changed, 37 insertions(+), 35 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 4670b741..a3dd8e9b 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -6,7 +6,13 @@ import KeyCode from 'rc-util/lib/KeyCode';
 import warning from 'rc-util/lib/warning';
 import classNames from 'classnames';
 
-import { TreeContext } from './contextTypes';
+import {
+  TreeContext,
+  NodeMouseEventHandler,
+  NodeDragEventHandler,
+  NodeDragEventParams,
+  NodeMouseEventParams,
+} from './contextTypes';
 import {
   getDataAndAria,
   getDragNodesKeys,
@@ -80,8 +86,8 @@ export interface TreeProps {
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
-  onClick?: (e: React.MouseEvent, treeNode: EventDataNode) => void;
-  onDoubleClick?: (e: React.MouseEvent, treeNode: EventDataNode) => void;
+  onClick?: NodeMouseEventHandler;
+  onDoubleClick?: NodeMouseEventHandler;
   onExpand?: (
     expandedKeys: Key[],
     info: {
@@ -110,26 +116,22 @@ export interface TreeProps {
   ) => void;
   loadData?: (treeNode: EventDataNode) => Promise<void>;
   loadedKeys?: Key[];
-  onMouseEnter?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onMouseLeave?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
+  onMouseEnter?: (info: NodeMouseEventParams) => void;
+  onMouseLeave?: (info: NodeMouseEventParams) => void;
   onRightClick?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onDragStart?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onDragEnter?: (info: {
-    event: React.MouseEvent;
-    node: EventDataNode;
-    expandedKeys: Key[];
-  }) => void;
-  onDragOver?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onDragLeave?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onDragEnd?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onDrop?: (info: {
-    event: React.MouseEvent;
-    node: EventDataNode;
-    dragNode: EventDataNode;
-    dragNodesKeys: Key[];
-    dropPosition: number;
-    dropToGap: boolean;
-  }) => void;
+  onDragStart?: (info: NodeDragEventParams) => void;
+  onDragEnter?: (info: NodeDragEventParams & { expandedKeys: Key[] }) => void;
+  onDragOver?: (info: NodeDragEventParams) => void;
+  onDragLeave?: (info: NodeDragEventParams) => void;
+  onDragEnd?: (info: NodeDragEventParams) => void;
+  onDrop?: (
+    info: NodeDragEventParams & {
+      dragNode: EventDataNode;
+      dragNodesKeys: Key[];
+      dropPosition: number;
+      dropToGap: boolean;
+    },
+  ) => void;
   /**
    * Used for `rc-tree-select` only.
    * Do not use in your production code directly since this will be refactor.
@@ -339,7 +341,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return newState;
   }
 
-  onNodeDragStart = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragStart: NodeDragEventHandler = (event, node) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
     const { eventKey } = node.props;
@@ -367,7 +369,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
    * Better for use mouse move event to refresh drag state.
    * But let's just keep it to avoid event trigger logic change.
    */
-  onNodeDragEnter = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragEnter: NodeDragEventHandler = (event, node) => {
     const { expandedKeys, keyEntities, dragNodesKeys } = this.state;
     const { onDragEnter } = this.props;
     const { pos, eventKey } = node.props;
@@ -429,7 +431,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }, 0);
   };
 
-  onNodeDragOver = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragOver: NodeDragEventHandler = (event, node) => {
     const { dragNodesKeys } = this.state;
     const { onDragOver } = this.props;
     const { eventKey } = node.props;
@@ -454,7 +456,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeDragLeave = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragLeave: NodeDragEventHandler = (event, node) => {
     const { onDragLeave } = this.props;
 
     this.setState({
@@ -466,7 +468,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeDragEnd = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragEnd: NodeDragEventHandler = (event, node) => {
     const { onDragEnd } = this.props;
     this.setState({
       dragOverNodeKey: '',
@@ -480,7 +482,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     this.dragNode = null;
   };
 
-  onNodeDrop = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDrop: NodeDragEventHandler = (event, node) => {
     const { dragNodesKeys = [], dropPosition } = this.state;
     const { onDrop } = this.props;
     const { eventKey, pos } = node.props;
@@ -526,21 +528,21 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeClick = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
+  onNodeClick: NodeMouseEventHandler = (e, treeNode) => {
     const { onClick } = this.props;
     if (onClick) {
       onClick(e, treeNode);
     }
   };
 
-  onNodeDoubleClick = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
+  onNodeDoubleClick: NodeMouseEventHandler = (e, treeNode) => {
     const { onDoubleClick } = this.props;
     if (onDoubleClick) {
       onDoubleClick(e, treeNode);
     }
   };
 
-  onNodeSelect = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
+  onNodeSelect: NodeMouseEventHandler = (e, treeNode) => {
     let { selectedKeys } = this.state;
     const { keyEntities } = this.state;
     const { onSelect, multiple } = this.props;
@@ -580,7 +582,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeCheck = (
-    e: React.MouseEvent<HTMLDivElement>,
+    e: React.MouseEvent<HTMLSpanElement>,
     treeNode: EventDataNode,
     checked: boolean,
   ) => {
@@ -708,21 +710,21 @@ class Tree extends React.Component<TreeProps, TreeState> {
       });
     });
 
-  onNodeMouseEnter = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
+  onNodeMouseEnter: NodeMouseEventHandler = (event, node) => {
     const { onMouseEnter } = this.props;
     if (onMouseEnter) {
       onMouseEnter({ event, node });
     }
   };
 
-  onNodeMouseLeave = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
+  onNodeMouseLeave: NodeMouseEventHandler = (event, node) => {
     const { onMouseLeave } = this.props;
     if (onMouseLeave) {
       onMouseLeave({ event, node });
     }
   };
 
-  onNodeContextMenu = (event: React.MouseEvent<HTMLDivElement>, node: EventDataNode) => {
+  onNodeContextMenu: NodeMouseEventHandler = (event, node) => {
     const { onRightClick } = this.props;
     if (onRightClick) {
       event.preventDefault();

From a48783bfcde1dc9ee99f6facb6ac7a1f706e7539 Mon Sep 17 00:00:00 2001
From: "Ivan.Yakovlev" <ivan.yakovlev@pochtavip.com>
Date: Mon, 3 Aug 2020 13:13:24 +0300
Subject: [PATCH 122/397] feat: types enhanced (#356)

---
 src/TreeNode.tsx | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index db687720..8e434028 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -73,7 +73,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     this.syncLoadData(this.props);
   }
 
-  onSelectorClick = e => {
+  onSelectorClick = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
     // Click trigger before select/check operation
     const {
       context: { onNodeClick },
@@ -87,14 +87,14 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     }
   };
 
-  onSelectorDoubleClick = e => {
+  onSelectorDoubleClick = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
     const {
       context: { onNodeDoubleClick },
     } = this.props;
     onNodeDoubleClick(e, convertNodePropsToEventData(this.props));
   };
 
-  onSelect = e => {
+  onSelect = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
     if (this.isDisabled()) return;
 
     const {
@@ -104,7 +104,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     onNodeSelect(e, convertNodePropsToEventData(this.props));
   };
 
-  onCheck = e => {
+  onCheck = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
     if (this.isDisabled()) return;
 
     const { disableCheckbox, checked } = this.props;
@@ -119,28 +119,28 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     onNodeCheck(e, convertNodePropsToEventData(this.props), targetChecked);
   };
 
-  onMouseEnter = e => {
+  onMouseEnter = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
     const {
       context: { onNodeMouseEnter },
     } = this.props;
     onNodeMouseEnter(e, convertNodePropsToEventData(this.props));
   };
 
-  onMouseLeave = e => {
+  onMouseLeave = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
     const {
       context: { onNodeMouseLeave },
     } = this.props;
     onNodeMouseLeave(e, convertNodePropsToEventData(this.props));
   };
 
-  onContextMenu = e => {
+  onContextMenu = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
     const {
       context: { onNodeContextMenu },
     } = this.props;
     onNodeContextMenu(e, convertNodePropsToEventData(this.props));
   };
 
-  onDragStart = e => {
+  onDragStart = (e: React.DragEvent<HTMLDivElement>) => {
     const {
       context: { onNodeDragStart },
     } = this.props;
@@ -160,7 +160,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     }
   };
 
-  onDragEnter = e => {
+  onDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
     const {
       context: { onNodeDragEnter },
     } = this.props;
@@ -170,7 +170,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     onNodeDragEnter(e, this);
   };
 
-  onDragOver = e => {
+  onDragOver = (e: React.DragEvent<HTMLDivElement>) => {
     const {
       context: { onNodeDragOver },
     } = this.props;
@@ -180,7 +180,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     onNodeDragOver(e, this);
   };
 
-  onDragLeave = e => {
+  onDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
     const {
       context: { onNodeDragLeave },
     } = this.props;
@@ -189,7 +189,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     onNodeDragLeave(e, this);
   };
 
-  onDragEnd = e => {
+  onDragEnd = (e: React.DragEvent<HTMLDivElement>) => {
     const {
       context: { onNodeDragEnd },
     } = this.props;
@@ -201,7 +201,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     onNodeDragEnd(e, this);
   };
 
-  onDrop = e => {
+  onDrop = (e: React.DragEvent<HTMLDivElement>) => {
     const {
       context: { onNodeDrop },
     } = this.props;

From b8dbfe3ed421bf44bf4ebaad59c247c8dff94a2f Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 3 Aug 2020 18:27:42 +0800
Subject: [PATCH 123/397] chore(deps-dev): bump rc-dialog from 7.7.0 to 8.1.0
 (#350)

Bumps [rc-dialog](https://github.com/react-component/dialog) from 7.7.0 to 8.1.0.
- [Release notes](https://github.com/react-component/dialog/releases)
- [Changelog](https://github.com/react-component/dialog/blob/master/HISTORY.md)
- [Commits](https://github.com/react-component/dialog/compare/7.7.0...8.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 932b9f18..33defad4 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
     "father": "^2.25.0-alpha.1",
     "less": "^3.11.1",
     "np": "^6.0.0",
-    "rc-dialog": "^7.3.1",
+    "rc-dialog": "^8.1.0",
     "rc-tooltip": "4.x",
     "rc-trigger": "^4.0.0",
     "react": "^16.8.0",

From c9dff0a82dc9209690674425a2ed1b8e1e7c9c9f Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 3 Aug 2020 18:29:56 +0800
Subject: [PATCH 124/397] 3.8.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 33defad4..cbd34a4d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.8.2",
+  "version": "3.8.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From b3fce6587f0255832e5d913b5ef5686b91c6ed26 Mon Sep 17 00:00:00 2001
From: Ivan Yakovlev <addictional2013@gmail.com>
Date: Mon, 3 Aug 2020 15:55:50 +0300
Subject: [PATCH 125/397] fix: wrong type(#356) (#358)

Co-authored-by: Ivan.Yakovlev <ivan.yakovlev@pochtavip.com>
---
 src/contextTypes.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 62e3d302..f880b01b 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -9,7 +9,7 @@ export type NodeMouseEventParams<T = HTMLSpanElement> = {
   event: React.MouseEvent<T>;
   node: EventDataNode;
 };
-export type NodeDragEventParams<T = HTMLSpanElement> = {
+export type NodeDragEventParams<T = HTMLDivElement> = {
   event: React.MouseEvent<T>;
   node: EventDataNode;
 };

From bb3978f25eeffbd76be659d40916c3e719e707d8 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 3 Aug 2020 21:02:52 +0800
Subject: [PATCH 126/397] 3.8.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index cbd34a4d..35773190 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.8.3",
+  "version": "3.8.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 9cf10d16277dd5d11357500408cdc27e25396642 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 5 Aug 2020 17:11:17 +0800
Subject: [PATCH 127/397] chore: Use `rc-motion` instead of `rc-animate`

---
 package.json           | 2 +-
 src/MotionTreeNode.tsx | 5 ++---
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index 35773190..68ca25d3 100644
--- a/package.json
+++ b/package.json
@@ -71,7 +71,7 @@
   "dependencies": {
     "@babel/runtime": "^7.10.1",
     "classnames": "2.x",
-    "rc-animate": "^3.1.0",
+    "rc-motion": "^1.0.0",
     "rc-util": "^5.0.0",
     "rc-virtual-list": "^1.1.0"
   }
diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 7c191434..e3f936a0 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -1,7 +1,6 @@
 import * as React from 'react';
 import classNames from 'classnames';
-// @ts-ignore
-import CSSMotion from 'rc-animate/lib/CSSMotion';
+import CSSMotion from 'rc-motion';
 import TreeNode, { TreeNodeProps } from './TreeNode';
 import { FlattenNode } from './interface';
 import { getTreeNodeProps, TreeNodeRequiredProps } from './utils/treeUtil';
@@ -17,7 +16,7 @@ interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
   treeNodeRequiredProps: TreeNodeRequiredProps;
 }
 
-const MotionTreeNode: React.ForwardRefRenderFunction<typeof CSSMotion, MotionTreeNodeProps> = (
+const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeNodeProps> = (
   {
     className,
     style,

From 97ca267a37319b296a8835f730cd1375e1828edc Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 5 Aug 2020 17:11:58 +0800
Subject: [PATCH 128/397] 3.8.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 68ca25d3..62165fe2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.8.4",
+  "version": "3.8.5",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From be3e453e7ebe3df98615fa97becd5dd3aa69caa0 Mon Sep 17 00:00:00 2001
From: zhangchen <zhangchen915@outlook.com>
Date: Fri, 7 Aug 2020 14:17:21 +0800
Subject: [PATCH 129/397] feat: add Indent end-first-level class (#360)

* feat: add Indent end-first-level class

* update: snapshots

* 3.8.6

* Update package.json
---
 src/Indent.tsx                                |  1 +
 tests/__snapshots__/Tree.spec.js.snap         | 18 +++++-----
 .../__snapshots__/TreeNodeProps.spec.js.snap  |  6 ++--
 tests/__snapshots__/TreeProps.spec.js.snap    | 36 +++++++++----------
 4 files changed, 31 insertions(+), 30 deletions(-)

diff --git a/src/Indent.tsx b/src/Indent.tsx
index a7ade7ea..11f4cfcc 100644
--- a/src/Indent.tsx
+++ b/src/Indent.tsx
@@ -22,6 +22,7 @@ const Indent: React.FC<IndentProps> = ({ prefixCls, level, isStart, isEnd }) =>
         className={classNames(baseClassName, {
           [`${baseClassName}-start`]: isStart[i + 1],
           [`${baseClassName}-end`]: isEnd[i + 1],
+          [`${baseClassName}-end-first-level`]: !i && isEnd[0],
         })}
       />,
     );
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index b4c76ccc..63565dc5 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -52,7 +52,7 @@ exports[`Tree Basic check basic render 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -83,7 +83,7 @@ exports[`Tree Basic check basic render 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -164,7 +164,7 @@ exports[`Tree Basic check check after data ready works 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -379,7 +379,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -407,7 +407,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -485,7 +485,7 @@ exports[`Tree Basic renders correctly 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -516,7 +516,7 @@ exports[`Tree Basic renders correctly 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
             />
             <span
               class="rc-tree-indent-unit rc-tree-indent-unit-start"
@@ -550,7 +550,7 @@ exports[`Tree Basic renders correctly 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
             />
             <span
               class="rc-tree-indent-unit rc-tree-indent-unit-end"
@@ -584,7 +584,7 @@ exports[`Tree Basic renders correctly 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index 1b2aac2b..12e44abf 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -664,7 +664,7 @@ exports[`TreeNode Props isLeaf 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -736,7 +736,7 @@ exports[`TreeNode Props isLeaf 2`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -811,7 +811,7 @@ exports[`TreeNode Props isLeaf 3`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index 17d5a410..185c7aa5 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -52,7 +52,7 @@ exports[`Tree Props checkStrictly 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -133,7 +133,7 @@ exports[`Tree Props checkable default 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -214,7 +214,7 @@ exports[`Tree Props checkable without selectable 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -316,7 +316,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -344,7 +344,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -656,7 +656,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -831,7 +831,7 @@ exports[`Tree Props icon 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -913,7 +913,7 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -994,7 +994,7 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -1069,7 +1069,7 @@ exports[`Tree Props loadData basic 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -1141,7 +1141,7 @@ exports[`Tree Props multiple 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -1268,7 +1268,7 @@ exports[`Tree Props selectable with selectable 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -1343,7 +1343,7 @@ exports[`Tree Props selectable without selectable 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -1754,7 +1754,7 @@ exports[`Tree Props treeData 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -1782,7 +1782,7 @@ exports[`Tree Props treeData 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit"
+              class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span
@@ -1810,7 +1810,7 @@ exports[`Tree Props treeData 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit"
+              class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
             />
             <span
               class="rc-tree-indent-unit rc-tree-indent-unit-start"
@@ -1841,7 +1841,7 @@ exports[`Tree Props treeData 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit"
+              class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
             />
             <span
               class="rc-tree-indent-unit rc-tree-indent-unit-end"
@@ -1872,7 +1872,7 @@ exports[`Tree Props treeData 1`] = `
             class="rc-tree-indent"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
             />
           </span>
           <span

From adaf8c77d68c797ca0e9238e8445e87b6a71a4a5 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 7 Aug 2020 14:18:18 +0800
Subject: [PATCH 130/397] 3.9.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 62165fe2..7c5d566a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.8.5",
+  "version": "3.9.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 06c281f1fb87612bbf4725b237675b5b0f2e795a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Fri, 14 Aug 2020 10:41:37 +0800
Subject: [PATCH 131/397] chore: Update rc-virtual-list version (#362)

* bump rc-virtual-list version

* replace deps

* fix test case

* add ignore

* update desp
---
 .gitignore                         |  1 +
 examples/animation.jsx             | 65 +++++++++++++++++-------------
 package.json                       |  2 +-
 src/MotionTreeNode.tsx             |  5 ++-
 src/NodeList.tsx                   | 12 ++----
 tests/Tree.spec.js                 | 46 +++++++++++++++------
 tests/TreeMotion.spec.js           | 27 -------------
 tests/__mocks__/rc-util/lib/raf.ts |  9 +++++
 8 files changed, 88 insertions(+), 79 deletions(-)
 create mode 100644 tests/__mocks__/rc-util/lib/raf.ts

diff --git a/.gitignore b/.gitignore
index a40be981..0e633495 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,4 @@ lib
 /coverage
 yarn.lock
 es/
+!tests/__mocks__/rc-util/lib
\ No newline at end of file
diff --git a/examples/animation.jsx b/examples/animation.jsx
index 288324d3..0b7c3233 100644
--- a/examples/animation.jsx
+++ b/examples/animation.jsx
@@ -88,36 +88,45 @@ function getTreeData() {
   ];
 }
 
-const Demo = () => (
-  <div className="animation">
-    <h2>expanded</h2>
-    <style dangerouslySetInnerHTML={{ __html: STYLE }} />
+const Demo = () => {
+  const treeRef = React.useRef();
 
-    <div style={{ display: 'flex' }}>
-      <div style={{ flex: '1 1 50%' }}>
-        <h3>With Virtual</h3>
-        <Tree
-          defaultExpandAll={false}
-          defaultExpandedKeys={defaultExpandedKeys}
-          motion={motion}
-          height={200}
-          itemHeight={20}
-          style={{ border: '1px solid #000' }}
-          treeData={getTreeData()}
-        />
-      </div>
-      <div style={{ flex: '1 1 50%' }}>
-        <h3>Without Virtual</h3>
-        <Tree
-          defaultExpandAll={false}
-          defaultExpandedKeys={defaultExpandedKeys}
-          motion={motion}
-          style={{ border: '1px solid #000' }}
-          treeData={getTreeData()}
-        />
+  setTimeout(() => {
+    treeRef.current.scrollTo({ key: '0-9-2' });
+  }, 100);
+
+  return (
+    <div className="animation">
+      <h2>expanded</h2>
+      <style dangerouslySetInnerHTML={{ __html: STYLE }} />
+
+      <div style={{ display: 'flex' }}>
+        <div style={{ flex: '1 1 50%' }}>
+          <h3>With Virtual</h3>
+          <Tree
+            ref={treeRef}
+            defaultExpandAll={false}
+            defaultExpandedKeys={defaultExpandedKeys}
+            motion={motion}
+            height={200}
+            itemHeight={20}
+            style={{ border: '1px solid #000' }}
+            treeData={getTreeData()}
+          />
+        </div>
+        <div style={{ flex: '1 1 50%' }}>
+          <h3>Without Virtual</h3>
+          <Tree
+            defaultExpandAll={false}
+            defaultExpandedKeys={defaultExpandedKeys}
+            motion={motion}
+            style={{ border: '1px solid #000' }}
+            treeData={getTreeData()}
+          />
+        </div>
       </div>
     </div>
-  </div>
-);
+  );
+};
 
 export default Demo;
diff --git a/package.json b/package.json
index 7c5d566a..7920a8a7 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,6 @@
     "classnames": "2.x",
     "rc-motion": "^1.0.0",
     "rc-util": "^5.0.0",
-    "rc-virtual-list": "^1.1.0"
+    "rc-virtual-list": "^2.1.1"
   }
 }
diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index e3f936a0..1ed82aa7 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -1,4 +1,5 @@
 import * as React from 'react';
+import { useEffect } from 'react';
 import classNames from 'classnames';
 import CSSMotion from 'rc-motion';
 import TreeNode, { TreeNodeProps } from './TreeNode';
@@ -42,13 +43,13 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
     motionedRef.current = true;
   };
 
-  React.useEffect(() => {
+  useEffect(() => {
     if (motionNodes && motionType === 'hide' && visible) {
       setVisible(false);
     }
   }, [motionNodes]);
 
-  React.useEffect(
+  useEffect(
     () => () => {
       if (motionNodes) {
         onMotionEnd();
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index fd7c163c..51a698ea 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -3,7 +3,7 @@
  */
 
 import * as React from 'react';
-import VirtualList from 'rc-virtual-list';
+import VirtualList, { ListRef } from 'rc-virtual-list';
 import { FlattenNode, Key, DataEntity, DataNode, ScrollTo } from './interface';
 import MotionTreeNode from './MotionTreeNode';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
@@ -98,7 +98,7 @@ export function getMinimumRangeTransitionRange(
   height: number,
   itemHeight: number,
 ) {
-  if (!virtual || !height) {
+  if (virtual === false || !height) {
     return list;
   }
 
@@ -166,7 +166,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
   } = props;
 
   // =============================== Ref ================================
-  const listRef = React.useRef<VirtualList<FlattenNode>>(null);
+  const listRef = React.useRef<ListRef>(null);
   React.useImperativeHandle(ref, () => ({
     scrollTo: scroll => {
       listRef.current.scrollTo(scroll);
@@ -174,7 +174,6 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
   }));
 
   // ============================== Motion ==============================
-  const [disableVirtual, setDisableVirtual] = React.useState(false);
   const [prevExpandedKeys, setPrevExpandedKeys] = React.useState(expandedKeys);
   const [prevData, setPrevData] = React.useState(data);
   const [transitionData, setTransitionData] = React.useState(data);
@@ -186,7 +185,6 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
     setTransitionData(data);
     setTransitionRange([]);
     setMotionType(null);
-    setDisableVirtual(false);
 
     onListChangeEnd();
   }
@@ -201,7 +199,6 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
       if (diffExpanded.add) {
         const keyIndex = prevData.findIndex(({ data: { key } }) => key === diffExpanded.key);
 
-        if (motion) setDisableVirtual(true);
         const rangeNodes = getMinimumRangeTransitionRange(
           getExpandRange(prevData, data, diffExpanded.key),
           virtual,
@@ -218,7 +215,6 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
       } else {
         const keyIndex = data.findIndex(({ data: { key } }) => key === diffExpanded.key);
 
-        if (motion) setDisableVirtual(true);
         const rangeNodes = getMinimumRangeTransitionRange(
           getExpandRange(data, prevData, diffExpanded.key),
           virtual,
@@ -289,14 +285,12 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
 
       <VirtualList<FlattenNode>
         {...domProps}
-        disabled={disableVirtual}
         data={mergedData}
         itemKey={itemKey}
         height={height}
         fullHeight={false}
         virtual={virtual}
         itemHeight={itemHeight}
-        onSkipRender={onMotionEnd}
         prefixCls={`${prefixCls}-list`}
         ref={listRef}
       >
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 449503ff..4fb3672a 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -4,6 +4,7 @@ import React from 'react';
 import { render, mount } from 'enzyme';
 import { renderToJson } from 'enzyme-to-json';
 import { resetWarned } from 'rc-util/lib/warning';
+import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
 import VirtualList from 'rc-virtual-list';
 import Tree, { TreeNode } from '../src';
 import { InternalTreeNode } from '../src/TreeNode';
@@ -107,12 +108,12 @@ describe('Tree Basic', () => {
 
     it('use treeData to expand the parent node when the parent node key type is numeric', () => {
       const Demo = () => (
-          <Tree
-            defaultExpandParent
-            defaultExpandedKeys={[22]}
-            treeData={[{ key: 11, title: 11, children: [{ key: 22, title: 22 }] }]}
-          />
-        );
+        <Tree
+          defaultExpandParent
+          defaultExpandedKeys={[22]}
+          treeData={[{ key: 11, title: 11, children: [{ key: 22, title: 22 }] }]}
+        />
+      );
 
       const wrapper = mount(<Demo />);
 
@@ -1343,14 +1344,35 @@ describe('Tree Basic', () => {
     expect(onCheck).toHaveBeenCalledWith([]);
   });
 
-  it('scrollTo should work', () => {
-    const wrapper = mount(<Tree />);
+  describe('scrollTo should work', () => {
+    let domSpy;
+    let called = false;
+
+    beforeAll(() => {
+      domSpy = spyElementPrototypes(HTMLDivElement, {
+        scrollTop: {
+          get: () => 233,
+          set: () => {
+            called = true;
+          },
+        },
+      });
+    });
+
+    afterAll(() => {
+      domSpy.mockRestore();
+    });
 
-    const scrollTo = jest.fn();
-    wrapper.find('NodeList List').instance().scrollTo = scrollTo;
-    wrapper.instance().scrollTo({ key: 'light' });
+    it('work', () => {
+      jest.useFakeTimers();
+      const wrapper = mount(<Tree />);
 
-    expect(scrollTo).toHaveBeenCalled();
+      wrapper.instance().scrollTo({ key: 'light', align: 'top' });
+      jest.runAllTimers();
+
+      expect(called).toBeTruthy();
+      jest.useRealTimers();
+    });
   });
 
   it('not crash if expandedKeys is null', () => {
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.js
index 775c0969..ca7d643a 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.js
@@ -1,7 +1,5 @@
 import React from 'react';
 import { mount } from 'enzyme';
-import { act } from 'react-dom/test-utils';
-import VirtualList from 'rc-virtual-list';
 import Tree, { TreeNode } from '../src';
 import MotionTreeNode from '../src/MotionTreeNode';
 import { TreeContext } from '../src/contextTypes';
@@ -44,31 +42,6 @@ describe('Tree Motion', () => {
     }, 100);
   });
 
-  /**
-   * Mock show item
-   */
-  it('`onSkipRender` should clean up', () => {
-    const wrapper = mount(
-      <Tree
-        treeData={[{ key: '0-0', children: [{ key: '0-0-0' }] }]}
-        expandedKeys={[]}
-        motion={{}}
-      />,
-    );
-
-    wrapper.setProps({ expandedKeys: ['0-0'] });
-    act(() => {
-      wrapper
-        .find(VirtualList)
-        .props()
-        .onSkipRender();
-    });
-
-    const { data } = wrapper.find(VirtualList).props();
-    expect(data.length).toBe(1);
-    expect(data[0].key).toBe('0-0');
-  });
-
   it('getMinimumRangeTransitionRange', () => {
     const visibleList = getMinimumRangeTransitionRange(
       new Array(100).fill(null).map((_, index) => index),
diff --git a/tests/__mocks__/rc-util/lib/raf.ts b/tests/__mocks__/rc-util/lib/raf.ts
new file mode 100644
index 00000000..58f2e577
--- /dev/null
+++ b/tests/__mocks__/rc-util/lib/raf.ts
@@ -0,0 +1,9 @@
+function raf(callback: Function) {
+  return setTimeout(callback);
+}
+
+raf.cancel = (id: number) => {
+  clearTimeout(id);
+};
+
+export default raf;

From 543737f8de2194bcc202627cfdfcb8dca5b35ec6 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 14 Aug 2020 10:54:54 +0800
Subject: [PATCH 132/397] 3.9.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 7920a8a7..66daaf08 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.9.0",
+  "version": "3.9.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 0e4374dcb05571c6fae3ae1a770e1ff2fe640b77 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 20 Aug 2020 14:15:30 +0800
Subject: [PATCH 133/397] chore: Update rc-virtual-list to v3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 66daaf08..4641693a 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,6 @@
     "classnames": "2.x",
     "rc-motion": "^1.0.0",
     "rc-util": "^5.0.0",
-    "rc-virtual-list": "^2.1.1"
+    "rc-virtual-list": "^3.0.1"
   }
 }

From f14103984418f012274137ba5228152411ba06e4 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 20 Aug 2020 14:16:21 +0800
Subject: [PATCH 134/397] 3.9.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4641693a..76ed77fa 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.9.1",
+  "version": "3.9.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From efc8f5d8f2425780ecfddc1a5f67aa605bba7210 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 20 Aug 2020 14:47:11 +0800
Subject: [PATCH 135/397] test: Update test case

---
 tests/__snapshots__/Tree.spec.js.snap         |  753 +++---
 .../__snapshots__/TreeNodeProps.spec.js.snap  |  971 ++++----
 tests/__snapshots__/TreeProps.spec.js.snap    | 2147 +++++++++--------
 3 files changed, 2038 insertions(+), 1833 deletions(-)

diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index 63565dc5..42e7d8c1 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -15,96 +15,101 @@ exports[`Tree Basic check basic render 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-checkbox"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox rc-tree-checkbox-disabled"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox rc-tree-checkbox-disabled"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -127,65 +132,70 @@ exports[`Tree Basic check check after data ready works 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-checkbox rc-tree-checkbox-checked"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="Light"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
+            />
+            <span
+              class="rc-tree-checkbox rc-tree-checkbox-checked"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="Light"
             >
-              Light
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                Light
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox rc-tree-checkbox-checked"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="Bamboo"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox rc-tree-checkbox-checked"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="Bamboo"
             >
-              Bamboo
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                Bamboo
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -208,34 +218,39 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox rc-tree-checkbox-checked"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="parent 1"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-checkbox rc-tree-checkbox-checked"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="parent 1"
             >
-              parent 1
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                parent 1
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -258,51 +273,56 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="00"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="00"
             >
-              00
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                00
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="02"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="02"
             >
-              02
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                02
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -325,107 +345,112 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="00"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="00"
             >
-              00
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                00
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="01"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="01"
             >
-              01
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                01
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="010"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="010"
             >
-              010
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                010
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="012"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="012"
             >
-              012
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                012
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -448,164 +473,169 @@ exports[`Tree Basic renders correctly 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="spe rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="parent 1"
+          <div
+            class="spe rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
+            />
+            <span
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="parent 1"
             >
-              parent 1
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                parent 1
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-checkbox rc-tree-checkbox-disabled"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="leaf 1"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-checkbox rc-tree-checkbox-disabled"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="leaf 1"
             >
-              leaf 1
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                leaf 1
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
-            />
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+              />
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+            </span>
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="leaf"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="leaf"
             >
-              leaf
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                leaf
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
-            />
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+              />
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="leaf"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="leaf"
             >
-              leaf
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                leaf
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox rc-tree-checkbox-disabled"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="leaf 2"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox rc-tree-checkbox-disabled"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="leaf 2"
             >
-              leaf 2
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                leaf 2
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -628,31 +658,36 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_close"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
-            title="0"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__close"
+              class="rc-tree-switcher rc-tree-switcher_close"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
+              title="0"
             >
-              0
+              <span
+                class="rc-tree-iconEle rc-tree-icon__close"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                0
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index 12e44abf..f34501b0 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -15,138 +15,143 @@ exports[`TreeNode Props className 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-            <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -169,35 +174,40 @@ exports[`TreeNode Props customize icon component 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__customize"
-            >
-              <span
-                class="cust-icon"
-              />
-            </span>
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__customize"
+              >
+                <span
+                  class="cust-icon"
+                />
+              </span>
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -220,35 +230,40 @@ exports[`TreeNode Props customize icon element 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__customize"
-            >
-              <span
-                class="cust-icon"
-              />
-            </span>
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__customize"
+              >
+                <span
+                  class="cust-icon"
+                />
+              </span>
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -271,28 +286,33 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-title"
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -315,143 +335,148 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          aria-label="0-0"
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            aria-label="0-0"
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          aria-label="0-0-0"
-          class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            aria-label="0-0-0"
+            class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          aria-label="0-0-0-0"
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            aria-label="0-0-0-0"
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-            <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          aria-label="0-0-1"
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            aria-label="0-0-1"
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          aria-label="0-1"
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          </div>
+          <div
+            aria-label="0-1"
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -474,143 +499,148 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-          data-test="0-0"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            data-test="0-0"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
-          data-test="0-0-0"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
+            data-test="0-0-0"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-          data-test="0-0-0-0"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            data-test="0-0-0-0"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-            <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-          data-test="0-0-1"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            data-test="0-0-1"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-          data-test="0-1"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            data-test="0-1"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -633,53 +663,58 @@ exports[`TreeNode Props isLeaf 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-title"
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -702,59 +737,64 @@ exports[`TreeNode Props isLeaf 2`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -777,59 +817,64 @@ exports[`TreeNode Props isLeaf 3`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index 185c7aa5..5d70c6ac 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -15,65 +15,70 @@ exports[`Tree Props checkStrictly 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-checkbox"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -96,65 +101,70 @@ exports[`Tree Props checkable default 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
+            />
+            <span
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -177,65 +187,70 @@ exports[`Tree Props checkable without selectable 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-checkbox"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -258,111 +273,116 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
-            </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          >
-            <span>
-              switcherIconFromNode0-1
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
-            />
+              class="rc-tree-switcher rc-tree-switcher_open"
+            >
+              <span>
+                switcherIconFromNode0-1
+              </span>
+            </span>
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -385,167 +405,172 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          >
-            <span>
-              switcherLeafIcon
-            </span>
-          </span>
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
-            />
-            <span
-              class="rc-tree-title"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             >
-              ---
+              <span>
+                switcherLeafIcon
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          >
-            <span>
-              switcherLeafIconFromNode0-1
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
-            />
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            >
+              <span>
+                switcherLeafIconFromNode0-1
+              </span>
+            </span>
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          >
-            <span>
-              switcherLeafIcon
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
             </span>
-          </span>
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
-            />
-            <span
-              class="rc-tree-title"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             >
-              ---
+              <span>
+                switcherLeafIcon
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
-          >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          >
-            <span>
-              switcherLeafIconFromNode0-2-1
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
-            />
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-title"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             >
-              ---
+              <span>
+                switcherLeafIconFromNode0-2-1
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          >
-            <span>
-              switcherLeafIcon
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
-            />
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            >
+              <span>
+                switcherLeafIcon
+              </span>
+            </span>
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -569,12 +594,17 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
   <div
     aria-label="name"
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      />
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
+        <div
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
+        />
+      </div>
     </div>
   </div>
 </div>
@@ -596,12 +626,17 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
   <div
     class="rc-tree-list"
     data-test="tree"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      />
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
+        <div
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
+        />
+      </div>
     </div>
   </div>
 </div>
@@ -622,59 +657,64 @@ exports[`Tree Props defaultExpandAll 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -698,31 +738,36 @@ exports[`Tree Props disabled basic 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -746,31 +791,36 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -793,67 +843,72 @@ exports[`Tree Props icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__customize"
+              class="rc-tree-switcher rc-tree-switcher_open"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              <span>
-                ROOT ICON
+              <span
+                class="rc-tree-iconEle rc-tree-icon__customize"
+              >
+                <span>
+                  ROOT ICON
+                </span>
+              </span>
+              <span
+                class="rc-tree-title"
+              >
+                ---
               </span>
             </span>
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          >
             <span
-              class="rc-tree-title"
+              aria-hidden="true"
+              class="rc-tree-indent"
             >
-              ---
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
-          >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__customize"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              <span>
-                CUSTOMIZE ICON
+              <span
+                class="rc-tree-iconEle rc-tree-icon__customize"
+              >
+                <span>
+                  CUSTOMIZE ICON
+                </span>
+              </span>
+              <span
+                class="rc-tree-title"
+              >
+                ---
               </span>
             </span>
-            <span
-              class="rc-tree-title"
-            >
-              ---
-            </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -876,65 +931,70 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-checkbox"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -957,65 +1017,70 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
+            />
+            <span
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-checkbox"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-checkbox"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -1038,53 +1103,58 @@ exports[`Tree Props loadData basic 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-title"
+              class="rc-tree-switcher rc-tree-switcher_open"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
+            <span
+              class="rc-tree-switcher rc-tree-switcher_close"
             />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher_close"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
-            title="---"
-          >
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -1107,59 +1177,64 @@ exports[`Tree Props multiple 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -1182,12 +1257,17 @@ exports[`Tree Props prefixCls 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      />
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
+        <div
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
+        />
+      </div>
     </div>
   </div>
 </div>
@@ -1208,12 +1288,17 @@ exports[`Tree Props prefixCls 2`] = `
   </div>
   <div
     class="test-prefix-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="test-prefix-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      />
+    <div
+      class="test-prefix-list-holder"
+    >
+      <div>
+        <div
+          class="test-prefix-list-holder-inner"
+          style="display:flex;flex-direction:column"
+        />
+      </div>
     </div>
   </div>
 </div>
@@ -1234,59 +1319,64 @@ exports[`Tree Props selectable with selectable 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -1309,59 +1399,64 @@ exports[`Tree Props selectable without selectable 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -1384,51 +1479,56 @@ exports[`Tree Props showIcon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_close"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__close"
+              class="rc-tree-switcher rc-tree-switcher_close"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__close"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -1451,45 +1551,50 @@ exports[`Tree Props showIcon 2`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_close"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
-            title="---"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
           >
             <span
-              class="rc-tree-title"
+              class="rc-tree-switcher rc-tree-switcher_close"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-title"
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -1512,146 +1617,151 @@ exports[`Tree Props showIcon 3`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
         >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-            <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="---"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="---"
             >
-              ---
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                ---
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -1674,12 +1784,17 @@ exports[`Tree Props showLine 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position:relative"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display:flex;flex-direction:column"
-      />
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
+        <div
+          class="rc-tree-list-holder-inner"
+          style="display:flex;flex-direction:column"
+        />
+      </div>
     </div>
   </div>
 </div>
@@ -1700,197 +1815,202 @@ exports[`Tree Props treeData 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="T0"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="T0"
             >
-              T0
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                T0
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="T1"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="T1"
             >
-              T1
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                T1
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="T10"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="T10"
             >
-              T10
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                T10
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher_open"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-            title="T11"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__open"
+              class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              title="T11"
             >
-              T11
+              <span
+                class="rc-tree-iconEle rc-tree-icon__open"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                T11
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
-            />
-            <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-start"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="T110"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
+              />
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="T110"
             >
-              T110
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                T110
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
-            />
-            <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="T111"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
+              />
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="T111"
             >
-              T111
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                T111
+              </span>
             </span>
-          </span>
-        </div>
-        <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
-        >
-          <span
-            aria-hidden="true"
-            class="rc-tree-indent"
+          </div>
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
-            />
-          </span>
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="T12"
-          >
+              aria-hidden="true"
+              class="rc-tree-indent"
+            >
+              <span
+                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+              />
+            </span>
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="T12"
             >
-              T12
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                T12
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>
@@ -1913,31 +2033,36 @@ exports[`Tree Props treeData 2`] = `
   </div>
   <div
     class="rc-tree-list"
+    style="position: relative;"
   >
-    <div>
-      <div
-        class="rc-tree-list-holder-inner"
-        style="display: flex; flex-direction: column;"
-      >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
         <div
-          class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
         >
-          <span
-            class="rc-tree-switcher rc-tree-switcher-noop"
-          />
-          <span
-            class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-            title="T0"
+          <div
+            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
             <span
-              class="rc-tree-iconEle rc-tree-icon__docu"
+              class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
-              class="rc-tree-title"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title="T0"
             >
-              T0
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                T0
+              </span>
             </span>
-          </span>
+          </div>
         </div>
       </div>
     </div>

From 4c489136561bfc77cacaeafbe13fd136246a7fe0 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sat, 22 Aug 2020 17:37:24 +0800
Subject: [PATCH 136/397] fix: Not update state when unmount fix
 ant-design/ant-design#26330 fix #330

---
 src/Tree.tsx     | 10 ++++++++++
 tests/MyTree.tsx | 18 ++++++++++++++++++
 2 files changed, 28 insertions(+)
 create mode 100644 tests/MyTree.tsx

diff --git a/src/Tree.tsx b/src/Tree.tsx
index a3dd8e9b..16a2db68 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -195,6 +195,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   static TreeNode = TreeNode;
 
+  destroyed: boolean = false;
+
   delayedDragEnterLogic: Record<Key, number>;
 
   state: TreeState = {
@@ -341,6 +343,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return newState;
   }
 
+  componentWillUnmount() {
+    this.destroyed = true;
+  }
+
   onNodeDragStart: NodeDragEventHandler = (event, node) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
@@ -997,6 +1003,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     atomic: boolean = false,
     forceState: Partial<TreeState> | null = null,
   ) => {
+    if (this.destroyed) {
+      return;
+    }
+
     let needSync = false;
     let allPassed = true;
     const newState = {};
diff --git a/tests/MyTree.tsx b/tests/MyTree.tsx
new file mode 100644
index 00000000..db4c1616
--- /dev/null
+++ b/tests/MyTree.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import Tree from '../src';
+
+const MyTree = props => {
+  const { treeData, checkable, checkedKeys, onCheck } = props;
+  return (
+    <Tree
+      data-testid="tree"
+      className="my-tree"
+      checkable={checkable}
+      checkedKeys={checkedKeys}
+      onCheck={onCheck}
+      treeData={treeData}
+    />
+  );
+};
+
+export default MyTree;

From c6410a4a9037042d5c3622a9cbe887c8a3b4b0ab Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sat, 22 Aug 2020 17:38:02 +0800
Subject: [PATCH 137/397] 3.9.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 76ed77fa..efdbed06 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.9.2",
+  "version": "3.9.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 40e102b4b991f2ecec1eb5913ec8038046f7c813 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Fri, 28 Aug 2020 15:34:47 +0800
Subject: [PATCH 138/397] fix: Motion check logic (#366)

---
 src/MotionTreeNode.tsx   | 16 +++++++++----
 src/NodeList.tsx         |  6 +----
 tests/TreeMotion.spec.js | 49 ++++++++++++++++++++++++++++++----------
 3 files changed, 49 insertions(+), 22 deletions(-)

diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 1ed82aa7..8169c8b6 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -11,6 +11,7 @@ interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
   active: boolean;
   motion?: any;
   motionNodes?: FlattenNode[];
+  onMotionStart: () => void;
   onMotionEnd: () => void;
   motionType?: 'show' | 'hide';
 
@@ -24,6 +25,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
     motion,
     motionNodes,
     motionType,
+    onMotionStart: onOriginMotionStart,
     onMotionEnd: onOriginMotionEnd,
     active,
     treeNodeRequiredProps,
@@ -49,14 +51,18 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
     }
   }, [motionNodes]);
 
-  useEffect(
-    () => () => {
+  useEffect(() => {
+    // Trigger motion only when patched
+    if (motionNodes) {
+      onOriginMotionStart();
+    }
+
+    return () => {
       if (motionNodes) {
         onMotionEnd();
       }
-    },
-    [],
-  );
+    };
+  }, []);
 
   if (motionNodes) {
     return (
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 51a698ea..0e6880d2 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -229,11 +229,6 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         setTransitionRange(rangeNodes);
         setMotionType('hide');
       }
-
-      // Trigger when `motion` provided
-      if (motion) {
-        onListChangeStart();
-      }
     } else if (prevData !== data) {
       // If whole data changed, we just refresh the list
       setPrevData(data);
@@ -318,6 +313,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
               motion={motion}
               motionNodes={key === MOTION_KEY ? transitionRange : null}
               motionType={motionType}
+              onMotionStart={onListChangeStart}
               onMotionEnd={onMotionEnd}
               treeNodeRequiredProps={treeNodeRequiredProps}
               onMouseMove={() => {
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.js
index ca7d643a..f82da868 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.js
@@ -104,17 +104,42 @@ describe('Tree Motion', () => {
     jest.useRealTimers();
   });
 
-  it('MotionTreeNode should always trigger motion end', () => {
-    const onMotionEnd = jest.fn();
-    const wrapper = mount(
-      <TreeContext.Provider value={{ prefixCls: 'test' }}>
-        <MotionTreeNode motionNodes={[]} onMotionEnd={onMotionEnd} />
-      </TreeContext.Provider>,
-    );
-
-    expect(onMotionEnd).not.toHaveBeenCalled();
-
-    wrapper.unmount();
-    expect(onMotionEnd).toHaveBeenCalled();
+  describe('MotionTreeNode should always trigger motion end', () => {
+    it('with motionNodes', () => {
+      const onMotionStart = jest.fn();
+      const onMotionEnd = jest.fn();
+      const wrapper = mount(
+        <TreeContext.Provider value={{ prefixCls: 'test' }}>
+          <MotionTreeNode
+            motionNodes={[]}
+            onMotionStart={onMotionStart}
+            onMotionEnd={onMotionEnd}
+          />
+        </TreeContext.Provider>,
+      );
+
+      expect(onMotionStart).toHaveBeenCalled();
+      expect(onMotionEnd).not.toHaveBeenCalled();
+
+      wrapper.unmount();
+      expect(onMotionEnd).toHaveBeenCalled();
+    });
+
+    it('without motionNodes', () => {
+      const onMotionStart = jest.fn();
+      const onMotionEnd = jest.fn();
+      const wrapper = mount(
+        <TreeContext.Provider value={{ prefixCls: 'test', keyEntities: {} }}>
+          <MotionTreeNode onMotionStart={onMotionStart} onMotionEnd={onMotionEnd} isEnd={[false]} />
+        </TreeContext.Provider>,
+      );
+
+      expect(onMotionStart).not.toHaveBeenCalled();
+      expect(onMotionEnd).not.toHaveBeenCalled();
+
+      wrapper.unmount();
+      expect(onMotionStart).not.toHaveBeenCalled();
+      expect(onMotionEnd).not.toHaveBeenCalled();
+    });
   });
 });

From efead838427862e326d5bddcbf5af006eeca32d1 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 28 Aug 2020 15:38:10 +0800
Subject: [PATCH 139/397] 3.9.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index efdbed06..f54f2c8f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.9.3",
+  "version": "3.9.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 614608ea8cd4802f5205d9aa4bc6f9079f69191e Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Thu, 3 Sep 2020 14:45:44 +0800
Subject: [PATCH 140/397] chore(deps-dev): bump typescript from 3.9.7 to 4.0.2
 (#365)

Bumps [typescript](https://github.com/Microsoft/TypeScript) from 3.9.7 to 4.0.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f54f2c8f..edfdeb4c 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
     "rc-trigger": "^4.0.0",
     "react": "^16.8.0",
     "react-dom": "^16.8.0",
-    "typescript": "^3.5.2"
+    "typescript": "^4.0.2"
   },
   "dependencies": {
     "@babel/runtime": "^7.10.1",

From f78da4aae9043b301c92af31a677c76df04761cd Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Thu, 3 Sep 2020 14:45:53 +0800
Subject: [PATCH 141/397] chore(deps-dev): bump @typescript-eslint/parser from
 3.10.1 to 4.0.1 (#367)

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 3.10.1 to 4.0.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.0.1/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index edfdeb4c..b46865ae 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
     "@types/react": "^16.8.19",
     "@types/react-dom": "^16.8.4",
     "@types/warning": "^3.0.0",
-    "@typescript-eslint/parser": "^3.3.0",
+    "@typescript-eslint/parser": "^4.0.1",
     "css-animation": "^1.2.0",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",

From 08a591110cc48ea0b15b46b9ea92932409cab01c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=81=8F=E5=8F=B3?= <afc163@gmail.com>
Date: Thu, 3 Sep 2020 15:07:46 +0800
Subject: [PATCH 142/397] fix: Tree should support onContextMenu (#368)

close https://github.com/ant-design/ant-design/issues/26380
---
 package.json            |  2 +-
 src/Tree.tsx            |  3 +++
 tests/TreeProps.spec.js | 22 ++++++++++++++++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b46865ae..0933b2f6 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
     "enzyme-adapter-react-16": "^1.1.1",
     "enzyme-to-json": "^3.0.0",
     "eslint": "^7.0.0",
-    "father": "^2.25.0-alpha.1",
+    "father": "^2.29.8",
     "less": "^3.11.1",
     "np": "^6.0.0",
     "rc-dialog": "^8.1.0",
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 16a2db68..76cad714 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -86,6 +86,7 @@ export interface TreeProps {
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
+  onContextMenu?: React.MouseEventHandler<HTMLDivElement>;
   onClick?: NodeMouseEventHandler;
   onDoubleClick?: NodeMouseEventHandler;
   onExpand?: (
@@ -1057,6 +1058,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       itemHeight,
       virtual,
       titleRender,
+      onContextMenu,
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
 
@@ -1126,6 +1128,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
             onActiveChange={this.onActiveChange}
             onListChangeStart={this.onListChangeStart}
             onListChangeEnd={this.onListChangeEnd}
+            onContextMenu={onContextMenu}
             {...this.getTreeNodeRequiredProps()}
             {...domProps}
           />
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index cd5572dd..eab85159 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -738,6 +738,28 @@ describe('Tree Props', () => {
     expect(onDoubleClick).toHaveBeenCalledWith(expect.objectContaining({}), node);
   });
 
+  it('onContextMenu', () => {
+    const onClick = jest.fn();
+    const onContextMenu = jest.fn();
+
+    const wrapper = mount(
+      <Tree onClick={onClick} onContextMenu={onContextMenu} defaultExpandedKeys={['0-0']}>
+        <TreeNode key="0-0">
+          <TreeNode key="0-0-0" />
+        </TreeNode>
+      </Tree>,
+    );
+
+    const parentNode = wrapper.find(InternalTreeNode).first();
+    const targetNode = parentNode.find(InternalTreeNode).last();
+
+    // Select leaf
+    targetNode.find('.rc-tree-node-content-wrapper').simulate('contextmenu');
+
+    expect(onClick).not.toHaveBeenCalled();
+    expect(onContextMenu).toHaveBeenCalled();
+  });
+
   describe('loadedKeys & onLoad', () => {
     it('has loadedKeys', () => {
       const loadData = jest.fn(() => Promise.resolve());

From 06963c4f45f0697a4110916b775a9bdf38e11af0 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 3 Sep 2020 16:18:38 +0800
Subject: [PATCH 143/397] test: Fix test case

---
 tests/Tree.spec.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 4fb3672a..5842c225 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1356,6 +1356,9 @@ describe('Tree Basic', () => {
             called = true;
           },
         },
+        clientHeight: {
+          get: () => 100,
+        },
       });
     });
 

From 86a6401a5dd96bc97077e4fd5432d5a535cbaff1 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Thu, 3 Sep 2020 17:09:35 +0800
Subject: [PATCH 144/397] 3.9.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0933b2f6..534aab04 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.9.4",
+  "version": "3.9.5",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 1a5ebcff51f1f9bc7c731000997f397de9e03224 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 18 Sep 2020 10:15:13 +0800
Subject: [PATCH 145/397] chore: bump rc-motion version

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 534aab04..666a088b 100644
--- a/package.json
+++ b/package.json
@@ -71,7 +71,7 @@
   "dependencies": {
     "@babel/runtime": "^7.10.1",
     "classnames": "2.x",
-    "rc-motion": "^1.0.0",
+    "rc-motion": "^2.0.1",
     "rc-util": "^5.0.0",
     "rc-virtual-list": "^3.0.1"
   }

From fa14b887691851b65abd24388cba5b0859160f28 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 18 Sep 2020 10:16:15 +0800
Subject: [PATCH 146/397] 3.10.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 666a088b..c2cb2e6c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.9.5",
+  "version": "3.10.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 7ba6e427354ee8e6fa3ae59d0df21d0746757da3 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Tue, 13 Oct 2020 18:23:08 +0800
Subject: [PATCH 147/397] chore(deps-dev): bump rc-trigger from 4.4.3 to 5.0.7
 (#383)

Bumps [rc-trigger](https://github.com/react-component/trigger) from 4.4.3 to 5.0.7.
- [Release notes](https://github.com/react-component/trigger/releases)
- [Changelog](https://github.com/react-component/trigger/blob/master/HISTORY.md)
- [Commits](https://github.com/react-component/trigger/compare/v4.4.3...v5.0.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c2cb2e6c..8b24bd92 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
     "np": "^6.0.0",
     "rc-dialog": "^8.1.0",
     "rc-tooltip": "4.x",
-    "rc-trigger": "^4.0.0",
+    "rc-trigger": "^5.0.7",
     "react": "^16.8.0",
     "react-dom": "^16.8.0",
     "typescript": "^4.0.2"

From 205f152f40b641dc74b07eb2676f2f486584a1d1 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Fri, 23 Oct 2020 10:59:27 +0800
Subject: [PATCH 148/397] refactor: drag behavior & allowDrop (#355)

* refactor: optimize rerender caused by dragenter & leave

* chore

* chore

* docs: draggable demo

* chore: wip

* refactor: drag style

* refactor: drag, wip

* chore

* fix: test

* chore

* chore

* chore

* chore: clean code

* refactor: drag behavior

* chore: remove unused vars

* refactor: extract dropIndicatorRender

* refactor: dropPosition logic

* chore: update test

* refactor: optimize drag logic

* fix: lint

* refactor: drag logic

* chore: use offset

* perf

* feat: allow-drop

* fix: lint

* docs: allow drop

* docs: optimize perf by remove logger

* chore

* chore

* chore

* feat: dropTarget & dropContainer

* chore: vertical indicator

* chore

* chore: snapshot update & lint fix

* refactor: clean codes

* chore: offset

* feat: function draggable

* fix: test

* test: coverage

* chore

* fix: lint

* test: cov

* test: coverage

* test: cov

* feat: first node top half

* fix: lint

* test: cov

* test: cov

* test: cov

* test: cov

* test: cov

* docs: allowDrop & draggable

* chore

* chore

* test: fulfill test cases

* docs: props.indent

* test: cov

* refactor: drop area

* chore

* fix: lint

* fix: LGTM

* fix: drag

* fix: drag over indicator

* fix: lint

* fix: expand logic

* chore: do not allow drag left on non-last child

* refactor: don't allow dropPosition != 0 on node with children

* feat: use css indent

* refactor: move indent to state

* docs: remove indent prop

* fix: dropIndicatorRender type

* refactor: default indicator renderer

* fix: test

* test: snapshot

* fix: test case `without motionNodes`

* fix: scrollTo test

* fix: cov

* test: use domSpy

* refactor: extract drop indicator render

* refactor: use single indent measurer node

* test: snapshot

* refactor: allowDrop parameter api

* chore: add comment for tree state

* refactor: remove nodeInstances by create props manually

* chore: add comment

* chore

* test: update snapshot

* refactor: remove abstract-drag-over-node-key

* refactor: allow first child to drop on its parent

* chore: trigger ci
---
 README.md                                     |   4 +-
 assets/index.less                             |  50 +-
 examples/draggable-allow-drop.jsx             | 115 +++++
 examples/draggable.jsx                        |  46 +-
 package.json                                  |   1 +
 src/DropIndicator.tsx                         |  34 ++
 src/Indent.tsx                                |   6 +-
 src/NodeList.tsx                              |  21 +-
 src/Tree.tsx                                  | 383 +++++++++++---
 src/TreeNode.tsx                              |  54 +-
 src/contextTypes.ts                           |  16 +-
 src/util.tsx                                  | 183 ++++++-
 src/utils/treeUtil.ts                         |   2 +
 tests/Accessibility.spec.js                   |   5 +-
 tests/Tree.spec.js                            | 482 +++++++++++++++++-
 tests/TreeMotion.spec.js                      |   8 +-
 tests/__snapshots__/Tree.spec.js.snap         | 127 +++++
 .../__snapshots__/TreeNodeProps.spec.js.snap  | 165 ++++++
 tests/__snapshots__/TreeProps.spec.js.snap    | 433 ++++++++++++++++
 tests/util.spec.js                            |  12 +-
 20 files changed, 1946 insertions(+), 201 deletions(-)
 create mode 100644 examples/draggable-allow-drop.jsx
 create mode 100644 src/DropIndicator.tsx

diff --git a/README.md b/README.md
index 5da49ae2..2184bca8 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,7 @@ see examples
 | defaultExpandParent | auto expand parent treeNodes when init | bool | true |
 | defaultSelectedKeys | default selected treeNodes | String[] | [] |
 | disabled | whether disabled the tree | bool | false |
-| draggable | whether can drag treeNode. (drag events are not supported in Internet Explorer 8 and earlier versions or Safari 5.1 and earlier versions.) | bool | false |
+| draggable | whether can drag treeNode. (drag events are not supported in Internet Explorer 8 and earlier versions or Safari 5.1 and earlier versions.) | bool \| ({ node }) => boolean | false |
 | expandedKeys | Controlled expand specific treeNodes | String[] | - |
 | filterTreeNode | filter some treeNodes as you need. it should return true | function(node) | - |
 | icon | customize icon. When you pass component, whose render will receive full TreeNode props as component props | element/Function(props) | - |
@@ -94,6 +94,7 @@ see examples
 | onSelect | click the treeNode to fire | function(selectedKeys, e:{selected: bool, selectedNodes, node, event, nativeEvent}) | - |
 | switcherIcon | specific the switcher icon. | ReactNode / (props: TreeNodeAttribute) => ReactNode | - |
 | virtual | Disable virtual scroll when `false` | boolean | - |
+| allowDrop | whether to allow drop on node | ({ dropNode, dropPosition }) => boolean | - |
 
 ### TreeNode props
 
@@ -143,3 +144,4 @@ rc-tree is released under the MIT license.
 - [jqTree](http://mbraak.github.io/jqTree/)
 - [jquery.treeselect](http://travistidwell.com/jquery.treeselect.js/)
 - [angular Select Tree](http://a5hik.github.io/angular-multi-select-tree/)
+
diff --git a/assets/index.less b/assets/index.less
index b7b778d3..47eb0586 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -13,7 +13,7 @@
   .@{treeNodePrefixCls} {
     margin: 0;
     padding: 0;
-    line-height: 20px;
+    line-height: 24px;
     white-space: nowrap;
     list-style: none;
     outline: 0;
@@ -27,22 +27,25 @@
       -khtml-user-drag: element;
       -webkit-user-drag: element;
     }
-    &.drag-over {
-      > .draggable {
-        color: white;
-        background-color: #316ac5;
-        border: 1px #316ac5 solid;
-        opacity: 0.8;
+    
+    &.drop-container {
+      > .draggable::after {
+        position: absolute;
+        left: 0;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        box-shadow: inset 0 0 0 2px red;
+        content: "";
       }
-    }
-    &.drag-over-gap-top {
-      > .draggable {
-        border-top: 2px blue solid;
+      & ~ .@{treeNodePrefixCls} {
+        border-left: 2px solid chocolate;
       }
     }
-    &.drag-over-gap-bottom {
-      > .draggable {
-        border-bottom: 2px blue solid;
+    &.drop-target {
+      background-color: yellowgreen;
+      & ~ .@{treeNodePrefixCls} {
+        border-left: none;
       }
     }
     &.filter-node {
@@ -56,10 +59,11 @@
       padding: 0 0 0 18px;
     }
     .@{treePrefixCls}-node-content-wrapper {
+      position: relative;
       display: inline-block;
-      height: 17px;
+      height: 24px;
       margin: 0;
-      padding: 1px 3px 0 0;
+      padding: 0;
       text-decoration: none;
       vertical-align: top;
       cursor: pointer;
@@ -73,7 +77,7 @@
         height: 16px;
         margin-right: 2px;
         line-height: 16px;
-        vertical-align: middle;
+        vertical-align: -.125em;
         background-color: transparent;
         background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAABhCAYAAABRe6o8AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAK0dJREFUeNrsfQl8VNX1/5l9ksm+ELJB2ANECGtYVEAQaZBSFdAW0dpaKbi0WhX9Va1/S/+K2k+1iCztT4sFW6lKkUV2RLZAQHaSQBJCMllJJtvsM2/e75775k3evHkzTCZEAubweczMu/d7ZzLznXPvOff7zsjS7nudhXZaxZd/kKXf//9Cwgkf1xha2QOnS2DzofNw5FwZjM/KgFkTh8Idw/tBz7hImb9xQsV1W9czJf73zTsPek7I5XL3oQCFQkkOBSiV3C2eG/rz9z19Q8Wh7T5+kX3i7c9g6ojekDs6A1796Vg4XVoPe/ILYMnKzbDmxQfZaaMH+pApVFy3Sdupp8cKH6rJ8QQ55pBjvPvcEXJ8To415LDzHbOXH/OAZLK2t/vBbbcFHOOz3LOeMViW5QgYLImwTcrai0MSrdm4H/708ztgwtA0D+6OYb1hysh+kDtuEPxjWx59jUIyhYq7lc2k38HaGk5KtmniR4Au7Z5g34cnZHLF6vTRkyCuzyCAuATurKF+kuFy0aSK4/uXsy5moZuIkkbI94RCplidlZYDvZP7QUx8LD3f1NA46Up1yaRz+qPLSZ+FhIRrvDxgsCTC22DIp1Kp6OORX42GM/ef8sLh9IkeTEwi4fNNyu5Lb7Hf4VW/ZXFaDRV3qxPQcjUfEoaNkWxrLi0CW1MvVhMzOOD74GJci8Nj4lZkzn6UfKAMgLkZdv7+JU/79P95B+IG3gaFm9auNjcZlHKF/EPxGPO2ZC2O0EStmD6aOL4oBixghGpo5EgWr4F+8QOgX69M2Hn889Wkr3LDvefoGPL2kE/syXgcYpRKlQ/5uD7eOFy74fTpj0R8/8kj+sOsCUNofykcThYHLQfhVwW/gi1VW8HG2iVxt7q5GCewLukjLCERmos/g7rjr7PCo/XKVuH6Xa1QqTjyWQwAVytg53tLYfrGWs+x8/+/QNuwD/Z1T9Ve065SoVxx94g5YNY1Q6O9Giz2Vjhy7AA98D6ewzbsg33dUzXnAYMlnzQBFXDn3rsgb8YhihOST0hS3jBwwLVbMM83c/xgWLfrJMydku2DO2g8CJ/b/gNmpQmWXXgL7HY7zB/8sA+us2zTgXNs3oVyv+3jhvSC2XdkyTp7HMZpB5axSy/ww7SQkDXc53ztqUMQ2XsmvW93Mov6jL2TEKwFoPEqrl4o6ahtfBXgvj9yjze+RumSkj0RLh/bt4g88CzqnXbXotv65IBN2wqt5gYyAsfvv489QG//2vo091zkn1wrhyEpo+Hk5SN0DCXvpYIhny8BORx9o7ZPhO9+fNyLfBfmnffBYdSKgUMwz4fR7ZN/2SiJW1exDkyEfGazGaw2B7x77B1YMPQRH1xnGZLmzYW5wBAPxDid4CREcNht4HTYyJfBBn/dWoTE6fRxGKcNXE5ru147YgQBxEOxaX0AWuoAHBbvjg7BuNhG+mDfsvxvHhISUE7G6BmXDk3WBrC5rFBUUsA1uOObMwWn6O2gfoOBdTYA9pWX5T3kIWCw5BMTkMfx5o98QhySA6NWDByu9XzHCrgUixTugfg58PaFZWAlH1JLcxP8aeybkrjONCFpdBHRUF9bQUnjsFlDHkdIvmDGwb7tJSBiPF5SIR+lJMsmV10Tmc+d4FmX4fSOz//PpwUkdIIyNoVihOPJlLJRKo0SjOYWcAHj8Xy88Y+XVj4KDnBCTFgSxXieK1jyyWRiAnI49HxCE5NPiMN83Z6TZUE935bDBbS/FG5G2gz4bf9nQW5Uwp9y3oR5Q+dJ4jqVgALS0CnGTRr+cSjjCMkXzDg8AdtzCAlIUwYOO9isZrBZuIM3vL/7yw30wPsO0sdlsZIp3+UQvw4H+RtsNguZjSx+Xyu22YgntVvtmINxeAgYLPmE+R5vnJxGu/7IJ8RhsnjH8WI4fF4f8Pn2nSyBTQfP0v5SOJ1KR9d8Zx87A49lPwaR2khJ3LXsxIkTbDC3kh++2/PFxPWgj1PS+0Pv/lmUQP7Gv9Y4CUnp7RoHp1PWaWnXIZyCzXbnebPJRDwXruUs9Ghb21k8gQhtw6ibLHksjOuiF/ksDDcGGcRKyP180Wx68MY/ttIvCxmDkpkbQ8l7svaSTwp3LfKhYWoEk8WYr0M8Rq1S5Fu34wQmlT07G6HirmWjRo2SBXMrZeih+GkXSVN84QS9L/Qw7R2H93zBjtPRKbimyby5qUafHR0RAbbmBuKZXBDJr9f37IHpT7m9IQnytDER0FyjpxivXGSdeXN9Y022JloHLfYmEoK4vJ7Pbuden4z4uxhNItQ311CMIA3TfvJ1BIdJ4p/njoOn3v8KXl6zHb49fZm4Zgb2nyqF332wGX617DOYP30UiJPJoeKC8YChmHitxpOmvVOweNptzzh8ENKeQ+gBF28oWllfkA9MeAKARgcOhwOq3+QiZD4arn5rFm3DPtgXMcLXsPP3ZSsvNpyCSCYW1BBGXreDEnbhiSn0wPt4DtuwD/ZFjMcDirfJgrVQcTyZMFmM+TpMmWDUyu/pLnl4ql8PFiruWh4wFBOS5sKpwx7S4JRK5oeQxhGSL5hxAqVhAmF4I7Fvw5kKwxvKo7teSx07BViVHhxNdaBfeg/nZNThoIojgUd8GuiP7gLsixivARuhofZC0xunlAdfy0qZAA2qKmiy14PdxX0x1XItxKgTIF6RAqcqDwL2RQz1irgf90M29IChkLCr5AHL85ezVy9tbtdrTxwwC3qNeVrG7wWP+CA/YtXMjFfG9UtaEjcgGzTRsWR9L6M5QScjA1uTAQyXTkFeSe2yX28tW3ryqTFGib3giIlLU19JHxW/pG/MUNBpogFUMpoTlDtkYLQ1QWnTeag40bDs0CuVS0l/I3JPdqPUMOvX/VM+NfcnDHqyLahqOV8G44dmwL1uVcuebf/VzH94geRXu1sNc33FCISA+J7pyNH3rbtSnxmSHD0pPVbXH9v1jabS89XN+17aW/lX8rAUl3yEgKwEAT1jjHqxxzOJAyInRaeG0zFaqsyldRdb9514u84zBqdFcIsRKj4mEQtDoh+nkYTkLWRVTBaSZDEJDIbcVu7Wie1W6LMsvY1QIeLQkjJzmAm/fg9mj4qCR0Yp4cP7tJB36TJsPnAJlqxUYCBhc/9RPkIG3OtF3KMEt9IXx7Z3DdiRabirjtMeQ0KhRyJELCREexGgkrgvsmBzbzfjtjK2k36B5no6BjkKCdHIGHWSY4BAUdMmRgiSRCwjyvGEiEMSrd+8Hf72eDrcNZDx4Cb3t8HkPlaYOYiBf372Een5Cx81TCi4zloDduVxgjWhJ2OXU3IY3EfQJlrGtWsMjoBuEpU7h4NcoQBFhO/OSNi5J8mHLfoC+MEJBQlF/cd74XhVC08i3AVwhg8CB/HWytbzoGw+CVMyagih5ZJqmPbiuj1gYBu7+pTwYdB6wGMLs6/LGEouE855MEoif3o+JJHLLsqgczgF7auk/cRqGDEO1244ffIkssTdBaxMxeXDokeBMzILNKUrYHLvavjxAC3tj6ICMa46YjocMebBuuLf0W25GelPQmzJmz64W90DXk89oEIuWz0pMx0GpcVBAiflg/pGmFSkN0zaX1ixnHGxAfWAoYzB7ZG5p8+AOkCXRLjvxqEaRkqKxW0oeuMwcLh3mJLinJpUD/k8pJZrwBk1nOJy+1+l/aVwSD6hGuar0q8kcZ2ZB+wK46AeMC5rhOThtKAesOCa47lY1+KYcO3qp340HIYMjAMj+Ug++FpPj3/n6ek5bMM+2DfYMYqauQPv+xuDEpBfSwXaE6YkEm0B8jiaLtg+0Yd8uDMixmHUOq4Xt0Z0cEGSb54qbhzF5SQ30P5SOFTDNBgMYBKoYaRwt7oHvB56QJVCseLROzPBwJDAshVgywE97PhpmudYv1dP27AP9gWRHtDfGLjli0czCQH8jcF5QHfgEFAHiCQS70HzAYfbpNQwYhymTPIuWbjna5X2Uor6AxRzVB/hpYYR4nDaramsgbraq9DS3AjPjXxeEnere0A+ES118HpA8WGsPtSGd9gXTRyQAmQxBVctHGGQdGivFXJ98DG2YR/sixiv1yAaw+bkMHZCODwOHNf7HYPzgO6oNaAOkBLJ6e0B3bhAahgxDvN1m884KQ4DB5nL5kNqxdVvKW5rcaKXGkaIk1LDSOFudQ/Y0a041AP26RELda0oEkDFimB6t3jfxz7YFzHC1yAeg8fh7dGTeg+hpcZQejyZ0xJwb9eFbp11+npAiuPUMMO+zPYRJIhxmCzGfB2mTDBqxYAD1244faIHQxLJLJXwTVkMbC5Ng5cFahghDgOO+QT30Nz/criTT0nibtWdEJvhNGurPwnhkYnQUnIlqNesigwDTVyUlxhBrlCOUqmV0NTgAifrHRpYbS54Ok+Q9CDeMSVeSTHCcf2NgXiefPx44jG4KNidr/OkWvjAgXgTFz3cJHIx3h5QhCvqfRuwh+8PiONVLTRf55DTqFVlugJK/eee6RpJtP5CmqQapr24zvJcN1oRba49CpFpCaAMTw76NTdePAtys9FHD2gnrDET19dGHi5/jOf01dy2b1pyPApRyRStAhewPnpAqTHM1J2Gtb1m8lg8hjsP6E4Wi8jHT58eErGMKA8YGo5LEv+C5vUwZYJRa06yhazdouj0iR4MSSSlhgkF11l5txupiNbE4VruIET16hv086giI8FqqPaagp1W83kSyGWjgspi95ZRWchijvdgP9vRCpFqOSGRE1xWy0VvGkiPgXjEfXpPpOexeAxKQPE2WbAWKo4nk0fVcug8PLnDvad7z1A6fYo92Pp1//QsOXjcFwT3wrdlkNMvA+524/Zs+69sfeFR2nH+wws6de12IxXR2oRsuFq4jkS6MSDzc722DwHDldBQ0uClhjEbajbr65uyI8KiocFI1pPUg3GEaTA0e+7ja4oI14K+vplivLyxaAzOIj2C2jmbbfD5rATJMbrVMG4PeK1bMe7l1dvYVx++nXo+saE065O8RpxaO3Wc2nMfs3IohoiE+KD/XkO5Hpqq9TB09gZOQRCelJzz3s6q2dkZUFjvAIPFQZXNW+e2Te2zvqiGuDAVZCaoYNOpMjj62+kprLm22uMR/IzhtU4k3xGpMZShqlpCxQk8GUzN/Qn1ZLuJJ8srcXuyNjUMCuFcUp7seqphbmZFdFTanVB+dA9oI4LXHmJfhhEs4Sx1DYaSM2/sUitfmzIwFfRyFupMDrjnX3raHE6mzBSdCtKilLDrgh6wL2K852rpMczu6RjH6OFnDDoFv56bLIypgf6TiQ65jEqqX95Y6ukaCKeOwTwj4sgU0+LywqElZeawuc9+AFNHpMKUoT3gsbv7gr7GCPlnC2DZ2m3w1lNzmNrCozLxFIy4F5d/QXG5BLfYF8fyuGCm4I6sAW+0Ijospp+MYXTspbz89kgHIDJxmOfRmFUn7fm/HvGO4+lVGrN93JLstDjIjNeQz1AJODnKwAkGsxW2nqsiHjdvWdnyX7+DGOGIHRnDqzbMtcgn8/cxSZAvPae3uw2g6pjeh3z/+no/vPDj4dAzVkXCczvU110FnUoBM4cnw9j+PeCLvXnwwF3jWCEJQ8V11hqwKyiih+Suvh75RxMhxdIygE/1j731THTGkEm6pHS6TWWq05c2Xz6/r/Ljl4Ravus2hrJd5JNgoCZBS75UMircczQ5vMj36O5HYe3da0mzzGvanfncB/D8rOEQHyGDxsYm8qY7qKQHnw8vNI8k0drdWanw6qovYOPbT+FULxPjHLEuiEiKapsFagjOyvrgOssDYn4OUyTSpqDt3+c4HTHijaiWj3ixQkKSFysBJLV8Ys93PcZQtod8MtHnieTrPTrD4+kqjldA+pheHvJ5uC1YLdIaL9mpkBSrhEZDE9iIFxMGQi6yesUjITERZowaQPoXwdwpo71wzhgWwpLCodqip3vCuC3Xt2d/MLMmiG2ReeE6ywNicjiYPN/3NU6oJpRVwUI2JD1gR8ZQctwJjnw+V7mx3ONH9/4c1k5dK0k+fnze9pDAYfKQHmCxWD2ez2tI8hivzDKZTDAsIx6253FEEuKiMmMp+YRqmGf7PweZyUOgubrJC9eZa8CuMM6Kb1rZ1ro6v+0NBRfg97+5A2JjY2X8+yvaRvPcb29tP946rAcMmnyit8VzJQCSbg+Zbqet9SIfTr+0XYDLLy2DBVMzoIG8aYFSQE5CwrSkCDhbWuWDQ5OqDfP32R/74G71vWAXw8BL8/p5Zg7+YBgXVDZY4W8F5L3aVUGWOo0sT0IpC6W2n4S1Ww/oS8AA5JP5MNCbXVLkqz5WBS5TW1JoTL8MqK4zgVbOXTfsj4TYVtXQCtkDUnxwaFK1YaRwt7oHZJ3cLCKswcPSrTG8pJJ7/C2TCsyWYkpCqXWxuLbfpu3rvNrDlTEwe8KjPrX9vL4IrGtxnC58xaNTMoFRkQWfg3jfZvdSza0HvK1PHKzdV7jaYDIr5TJ5W33AoMknmoJl7j8HPZ/QfMgnDEImZMLpigbQasNAofC9eJ1/LVqtFs5fMcAUsp4T48zVRugb399LDTMkfSgYq4w+uFveAzq8lzE8+Rhyh+G2NaB30SHQl1RDQUGBlOfzqe23fsZJr+Nv0/ZJ1vYTTrsd0gMGSz7xO+NscYKeBB6UhHev9Us+IW5CVj/49lwVNFoZCA/XuasoeC8BwsLCwOiUwb4z5TBh2EAfnKOKrBEJ2XDN99Hsj2BIGkc+W4XFBxeMx7leOyo3YhzGYfd4PtThIflMxPsYyREbEwY/e2AW3Dt5FrBkWm5ubvZd6thdi7BeH1/bz2Zryz1iXT/+oG2kD/ZFjOg1SOoBUQfIawID6gFDIR+PY5oZT57vWuRD+2bHZuWrj98Dh4uugkWmhuiYGEo4lPNrNBqIjo4mLjwMjpc2wgsL7sb+Gikce5WF+rw6qDlYBXWHa4CtZSRxt7wHtNuJp+M+dCQeHrwipcUKEElWIj2HAiWglAlr+1mxhouzLe949NBBepw8eoq2YR9a2y9IPSCSDvWAQn2gWA/IETAE8glxTiOSsJISLxD5+C9MbeFJ5cw7RsCqbefhVIURXJoI6NkzBeThUXCuygJ/21EAU8ZkwdXiUzpB1BQq7tb2gMRjoYdxuPmF5LM6uIO2IzldeCtNQGFtP5uVrKfNjZ42fgr+eNoB2oZ9VGEqT20/D4l5PSD53FHzhwdvSEL+Md5iH7VapAcUb5MFa6HiKJkunVKsX/oErYzwlagywj8emEErI0iQKFTcLesBGeKZcL2HJOTJR3dX3Ao4/OydDHftiN+9aHdtPzKHgEKw8/KH0p+K3CVXZpev7ee1m+NHU4jG6wIl9YDiH48J1kLF8Tb/4QX4tZDhpZNSl0/iPq5QuCDY170m7vuIXrtMjWi7DcxubonJh+f5c5iukSQfV9svG99UK+O992xymL0ehynCweJsq+3nWUcG0BSiHtCzWyWlB/y+1TACcgVVG0ZIQt46Qw3TXusqNaJd7qAhEPnwnMspTcBAtf2qL7d9MRJSe/rU9vN4OD96wDmb6wW9IiX1gJ1WG6YRVPju4CIFoi01XjgkFdaGmbiIqw2zYKQSls8Og2MlZbDtYDG8vEoBq16YZyP9JNUwC9/hasM8QnAf+OK+NzVMV6gR7SJRsMPpSz7P1Mhw60B/UzDW6Yv7NOrVcRHToRkMYMTPT7AG5O2Fs/fT2n55DTu52n6COLjo3cUrY9J2vjo7OwLqyQyOesCZ/6n2eh5eU5igYWBTQT3FwBsPdE5tGCTfhejxnu2SwZX/8YIhiT7dvB1W/yId7uzHgNPWQr6hdsjp7YTx6VaYMdAJ6zd8DPPnPeajhgkF11lrt65QI5rBKJj1Jh8SzsG0BSH2AASUqu23+PjdPrX9eir7+NT2a5tbO6gH5En08fZGdy4u1ic5/WC/7ZK1YertRtiebyZ91ISDsZJqGJngumBUtdxOPN8qQqLbCYlMNgYssj5gDUsBhaUMtLaLMDa1hoZ1i9/dAPtXPONRwwhxlxSJYIhty/XFGKsI7oAPLlgP2F5FNP3z3Z6PtxROfUSlWf7GD2Yc3oIZx2FqhQ/eWndNomKR8fDwcKkm+77flb8zcSmjsY7aTWv7pWnI36EV1PYzN8Hxpt18bb93xEFeh/WAvAcLuCcsURsGyVcA8dB7THxANYy4NsyPyfR5ByGRmZCvUT0STGYH2IzkGyfrCVpCxNjmrwmZ9DBrQAMcPIM1XkZ44YqRfJpYbzVMfH/yLR8PYx07vXDBesCbtUb0b56aAiUlJVS8Ech0ul7Qr5/fS1VNXNHIyk9HvVgTTG0/yTFC1wO6p08pz+fRAUrVhmGMAIr4a6phQCABx4AD13wMmT7R8yH5mpqN5A20YIKTvFFhoFT2B5WtEu7ua4B/H75AiSTEoefzp4ax62VeuM60rlAjOjU1VUaOjv4pIdX2E3nB0PWA/Not0J6wVG0YcBg9ktaAahhhbRgS7WLAgWs3nHbR85lNVjAaLfT58LnDY3uDkyxsRiY1wbO7rvjg0PyqYUS4zrSuoIjuMPM6UNuPtw7rAfmAI+CesFRtGDq1BlbDDLn0IURaUBqVSc9jqgWjVgwccM2H067MrXPgvwBy02V6XfF31ToYN7S3Dw7NnxpGjOss6yqK6GXLlmE8mivVRqbce+fMmRNwHdw16gO6o92AOkCJ2jAyTFy61TD+pFg52iovHOb5MGWCUSsGHGHEC+K0yz03mYJJqB5mLCQvzAK7SlMgd+oQHxwGHLwa5u1j73JqmLShENZQ5oPrLOtCiujcJUuW3CvV8Pnnn+PBXouEbruB9QHdqZaAe8IStWFi7FdhcP3OwGoYidowm88r4FCxEzTOGoghAUecvIK82HBIVNdAgnEnRDDlcKJSA9suJ8PtgtowPC697gBENZd7qWHCGy5DSvkWH9wP3Qj5KAkD5hJDrO13Pcbwqg3jSbUEKrMhXD8QXIyzkeb5ClLnek271POpfXFYuWDl8/NYzNexDhfkkGgXAw5HK0vTNUqwwokqDXxe2AP++uwc2Pv1JjkmlH1wJNrFgMPBBMZ1WxsJ/XhCLy0fKmj4ZSHKqe4YnUbPRak4Ld8HO0+vIF7s76KAJOQx5O7NvA7Vhom2VMOQK/+AIaV/a1vzBcBhknj+vJ/D01tS4I974+A7PQtKVxOcqSZrmkMp8Ny+LHjoocVQV3RM4Y7QOoT7IZt7Gubv+7wnUvUBSUxHD17Th+faWx9QWBcQ7+M5qTE6qTZM5jWxtYXHZJgsxnwdpkwwas0hgcNMsnZ7nkyfxIN5KiOIcd9++Bu6F7zx0HlYwteGmTYUXhBVVOj2fHPEAcsWcR8vLR8h3ZlCwTXcQ7gKqVglYVhmGtQ5OS3fN7Iyr98LFo+BhuMI6wLyJh7je1fDDByQDGNypnleO+bqpPJ1/PSZf3Q3SOzrXjc1zK1ieCESf3kDf421MNVyZdNKmGTYf2/ekv3oBVeOW7aNrsPEtf2E9fx4w3NP57naVR9QXBfQM2mK6wOSD7jdUxUhkCxUnJBUST0zWLO5FaxWE819KVUa0Gp1EB4eCbU1ZV4E5zHtwQmI/oMgoERejz4u/2oV1Odvh3ELngWXTAHHPnkXpz9PIOCt5QuTHF9Ky+eVQLymHtAddEjVB4xLaGNrW3VT6Z9sKCpoK8cbKi6t1+AjrS0N45qb60Gni4aIyDhXz56p8pqaSpfdZpbj+eiYHmxkVHyevrxgfEdxPyQC8rf8FYdIPsOJnTDup08CU1cGNWabaBnvreUT6vf4un78ufbUBxTXBeRNsj5gsCSS+6lDJ4XjZgDWc8mg0JBEKEGKjU12pqX3VvLpoLS03vRWX1HubG2tV2K/64H7oRAQ32uGYTzk029ZA00nd3PkM1RBpcEAVfn7odFsX+/xTpL1AT10gfu/4jR9cvJ5tq8+oHddQN4k9YDBko/+XkgQ5JOTV4uPS4vPwMDMkV44nD7RUwlI5GNp6b2Uej04Gw1VSuyPX+hQcZ31gXcVRTQ/zSLxuAvSuduaHR9By6m9PuSrbDJ/OWfN/oXscg4rpeXjLx/hNX18bT+xlo+3joyhbA/5xJ6M/n4I66KOCL91YvJxfbxxuHbD6dMfiTxkSuultNtMtL8UDn+awWhsBZOphawDLZCQmAKJPVJ9cJ1lXUURzXs/JB6WNMHLKivOvwEG6wbodddMYFobPOQrtmlrFqz5+hEQKlo6oOW7HmMICHht8kkTUAZ1NWVkfTbIh3xCcnsiIhI44NrNswsTwNSacFdLS4NcCmc0tpB2Hfmg7GCzGqG6uowSUIzrTOsKimg0/Kzw0la1Wk01f6f1G+BHD34KX3/2M7BEtYIzn4SefUZDSa3iJMBGLzlVl6gPGCz5fAnYNrXqy4ugb/9hXuQbkpXjg8M3FwOHYN5YGmBUFUvizKZW8o13ksNKK34K1xlCXKcSsAsooo1G4zfLli3zOjesB9C94WG3vwJnDi6FBtvkGiSf0+nc42eYG1sfMFjyiQmIOOGGgxT5VCq1Fw5TJhi18oFDIMN+pL9cCofEsxDPh+TDD0qjDZPEdaZ1BUX00qVLscwFBhVa/tyHr2udxPv9BO9fLrdtfvL9jS8Rz4fyqCbJ9NiNrg8YLPlkMrmP68do15/n48knxGG+DlMmwXzA2A/7S+ESEpPptMuTLzk5QxLXmXajFNEFTw6HwStO8wEIztM1oiHvEz5Y/Afp5z2/Vw7rhqqAcdkBLxmxbwU7+TyRqK3k7RtLlz4muIQvEadStXYEoM9RyNUE64Chd3FrvA7rAYMln7iQEI/DKAyj3YuF30mST4jDZDFGs5gywajV3wur1Jc7TaZmZXR0giQO13v8mi8QrlM94A1URCMJ3Qk/uvMvV2t/YW+8mnbbP0rfEPa7+MLtH9gbagsUYeErhOd5AnMsBvJ5AUdCGyaLFSN1UWn/pgQ06uc4GeaoWsP1kSqw0GE9YCjkE+OQhNciH93LrSmTYbIY83WYMsGoVYpELS31So0mnPbv1bt/yLjOtBuliHZzjouA7fZ0xmb+feyI4Y9oe6SEnX2sX8/bPi6huxyXXph4OPXBpwdXf7k6xlJdEaEM1y0L+EJYemjkSuXc2KQH6be7se79ueBkTpHzwXyrQqsPGAr5OoLDnQpMFmO+DlMmGLUKdzTQgyGJsF9zU12HcZ1hN1IRjcliBXlvXYSFrItZGNM/a2Hi8DGgTeoFFV+tXXRyflqkKkx3T8qMuYm6qHDIePAJKP/io7dMZRcjlZExr0jnEnFGkxHis1qNWjU9PDqHfnh432Gz/ZG02QIVFA21PiAloHCbrD0WKo7fJuP3dDFlglErBg64dsPpEz2YmESh4jrDbqQimpbZUCh0MmCfiUzNeDx13F2gwKXglTOQPu0nwNrMD0cNGgYxWSPJlEPen6gEyJj3K6jY8eXvLZeLFCzretntSbWEwoPJbSznT1gzmbz6RsUPSpYrjPS58L7NdmIWacPoNZzyHthGcovFBvk8kaQekNcCYid/esAf/C8l3Yz2wOA42Su3J8+K0Cg39X7gCVBXFQJgVSvCHohPRdZw921mEj6Ygf5YS+YYEpemwvkX5trlSnU6WQPWnd8jGx4eHb9RE5auZom3ZZytjFyh08T0mJyg1XG/fmM1GZmmum/qXYzJplBGKmTAgM1SYTc3N9w3dCpLF5KjPjj2mylZfd7r1ycRqgXSqzcygUq5cka0aQaSSVxccvkq7Dt3+bcnnhr7vrL747z57MvCRjA5mJo19/YFFaafYhKANRroJRXQWEtIZ+MWdCzNygPoIsBRrYeGvV8DYzbukkfFUXLlnwDn+Amy2KSMB2M0ukHEtVUC66zFbAkwjhLOtWl7KHr0mpkkUyaBXJYKNlMRVBT+uQmxQ6fya1JfPSBvQj0hmlgPKO/+OG9KY3eUtJx5YsvlJaUbPoRWQyPIIuOAddi5MNWMhQYc3E44kjAsBhrPnYKGA9s+VIZHPk/O0A3al96G4l07DM8e27M8z1C9lZWzRmCZCkK+88Qb1nEHuY/nsA37YF/EINYTC0jUB5SqEei3PmC33XxGok3rjpLmtxd/flb2bmvrW7fNnAtMSyOZSO14Fbe7Lje5lWPiTg21B7aBXKVaK1NpCoHlyFHbAPZn33T9KzG2quS3j3yy5LHHh98TlTxM6cLC5wy3ly5TRIJcowBD+RfOj/9+esd7nziWXW2EY07G+yJ1Xz0ggJQmUKwH7PaAN6E9MTIRsnvqIE6riOyXGJGYkZWNmjwy81ro3jhrxws7rJz8GNeBhJg9J9xDSMVsIeQTRjwsIZKtzgAHNu93vH7hfGmpSmEFp9PEJafJgffxHLZhH+yLGBBsgbn1gNT7ovaPP3hDbaDnnNNJyGiR1gN2281hU3pHwsS0yORkjfPtuyeOfJiJiQVTTSklm8tBQk2tjn6wMpZEBFgvtr4cEsdMhLDBoxIr/vXXveTMIEzx4Vg5I8iDPgC/ewI00Yk6tdFE/KcslkyTHL/sWJyInMvoq1Ov+JNB8+c1AEWXAY62VW7zqwf0rRHoqwfs9oA3oT2+pQylvrGT+8U9DGNng8liAauhhu6L4+/yyXQxQEILLlmNsjRTE0BFAYQlpQKZXhPJWbp39uv5AB+9A/Dko6B2srrJkfFjeqq1yYQkPaCp+rITD7yP57AN+2BfxCDWk457d/HK/LJ6qvXTkfDGZneAxcrVCMRbPPActmEf7Ev1gN0EvDnN5HDBL7eU1fzv2eZv2ILDINfFgiw8FhjycWrTB4PVwQJTdRlkvQbT9R/EJ4NLGwtV/1lpIfTED/4cjvPWyyRAJsu0pARI6ZEYkasN76O1m2ohf//emvf/XLIWD7yP57AN+2BfxLz1suAF8XrAC3roH6MkHZSglrNktmXogffxHLZJ1wfstg7ZjVBHMy62edHWy4vMrV+uXJw7drI2dSCZL00gNzZB6cmjrrPl9ed+Fh45TJZ1OzhbGqDuzHFoLS9ZJVMqn+PHK6twLwQB1Ep1i9pS/N+WndsNez78pPGTcAUcxLYt31ZtWfzIlkemz4ibarO0qMmyUo0voIkE2sOHcvjr93vB3RaS3SB1NF7tf+l33zb80gbfLX8uF3Ihawprzd9y4Zktxa8eqbaesjI7P1sgU4ypb7VC/ZkjW+UqzUrcv+ft/oWeu2VapeWxIRklg04WwemSSii+8zau4fhZ+O9f/rfx3DcHG4dfKIMiqxPKeFCJdwGyDv5ecLd1yG6QOhpJeOV/vq193Ow4/qdfGh2x4S31G/brLRvpWnFH9cNNlk1v3De6f6E6Ivpt4pLMwp2v0jZni97oXEEpFJJWGr7mFbY9CRKytBLK+DYp69jvBXdbxwl4g9TRhFCMO7H8C885T80CwFTHQ/6ea/HixfQXqpzkOd3XlTjdAhKVUqmkekDSdgyoHpB1cuonOZXh4fUnvHW8PmC3ddiCUUeHMg5vwnE6Y/+e13XixU3k/sjExESqB6ypqZlDzh3Fdr7P9bRuAl4nC0Yd3d5x/KmjPUHJx4X+hkGpE1Y/wIjXq5xa3mPXrNujIUSbO3r0aKoH/Prrr+cSAqLi1NYZ71t3GuZ6ecAuUC9aYIs+4Yi2yE3Ga5qggIBWrVZPz8jIkOGB9/EcLzruJmAXtcDq6NDG8VVHS3o6VuKAQjPAH+cHJiFZ72kJqbAy1F3kmEYeTyDeb1ZqamoyrvHwwPt4DtuwD/ZFDGK7p+AuYjdQHb3ovQWZoBddKGkm8UGJOwR4dV4m/HFDIV/Pb7HI6w0KDw//Ii4uTo3Bh9VqZTTEBg4cGNvQwF17jvdJgPKujZhWq1WgFzQYDPaWlha88Ol0NwG7gN1IdXQx4cmFAPGmiawIXpydCW9v8iVhZWWlMyIiIpas92KSkpLoD1objUbiee3AE1Cn0ymys7OTSD/6W861tbWwffv2JsR2e8BuAzMhWKvZfzsVVRGP+JcHM+HZzwq9yrLt3r27mEyzz5rN5oUTJkzIwd8cQRIS7+ZZ7yEho6Ki6I+Jnz59mj18+PDR0tLS1fv37y/uJmC3gYXEJiYz47ddp1ZAShgg+cBhbvmHl3c0mezEm/2LTMMlly5dWjJjxox7evXqpcRUjM39K5xIPAxAvvvuOyfpu+PQoUPLCGGPkWnZ3k3AboM0HSFhtPelm612BqpbuURxZqIC1uwrhNbK0i8vvDrzKXjSK5JlCZFshIgHCgoKLH379h2QlpY2kKwFaXKaj44xSX3x4sVS0ud10vf49YyGuwl4E5u16er6d3bCfKm2H93WDyI0cvjnEQ/5Hsn5qMCnrgv+zFdCQgKMHz9ek5iYqMbIlwQbwO8Z81W3sC03N1dz5MgRqK+vx/VjNwF/6Hb6uTtRTvAazrTC84RoZ7J7quDNXYHJR4IPGDt2LAYdaqVSOblPnz49MdDA7bmioiLqAgcNGqTEilvYRqLfyWPGjMlXq9X2Y8eOdRPwh25uUpVKecY3d8H8QORDmzZtGqZesKxbSmRkZC7xcloMQI4ePVqTn5+/FfsQbzczJyenJ7bFxsbmtra2YiGkMsR2E7DbAnlG1P2Z/JEPrampiV/nqck6T028Wsu5c+f2HDhw4BPiBakekKz9tpSXlz+SlZU1lUTIahKc8DnD6/Jauy9M/wFbXFwcfxen4IHEyw2qrq4+3djYWNy7N/djj1euXAHi+fonJycPv3r1ahEJTlBhQyNgMiV3E7DbOvDh+9buwRmRrv2EQYi4zRNCXwfudBOw226o/Z8AAwBphnYirXZBiwAAAABJRU5ErkJggg==');
         background-repeat: no-repeat;
@@ -187,7 +191,7 @@
   }
   &-node-selected {
     background-color: #ffe6b0;
-    border: 1px #ffb951 solid;
+    box-shadow: 0 0 0 1px #ffb951;
     opacity: 0.8;
   }
   &-icon__open {
@@ -209,8 +213,16 @@
     margin-right: 2px;
     vertical-align: top;
   }
+  &-title {
+    display: inline-block;
+  }
+  &-indent {
+    display: inline-block;
+    vertical-align: bottom;
+    height: 0;
+  }
   &-indent-unit {
+    width: 24px;
     display: inline-block;
-    padding-left: 18px;
   }
 }
diff --git a/examples/draggable-allow-drop.jsx b/examples/draggable-allow-drop.jsx
new file mode 100644
index 00000000..8570e395
--- /dev/null
+++ b/examples/draggable-allow-drop.jsx
@@ -0,0 +1,115 @@
+/* eslint-disable no-console, react/no-access-state-in-setstate */
+import React from 'react';
+import { gData } from './utils/dataUtil';
+import './draggable.less';
+import '../assets/index.less';
+import Tree from '../src';
+
+function allowDrop({ dropNode, dropPosition }) {
+  if (!dropNode.children) {
+    if (dropPosition === 0) return false;
+  }
+  return true;
+}
+
+class Demo extends React.Component {
+  state = {
+    gData,
+    autoExpandParent: true,
+    expandedKeys: ['0-0-key', '0-0-0-key', '0-0-0-0-key'],
+  };
+
+  onDragStart = info => {
+    console.log('start', info);
+  };
+
+  onDragEnter = () => {
+    console.log('enter');
+  };
+
+  onDrop = info => {
+    console.log('drop', info);
+    const dropKey = info.node.key;
+    const dragKey = info.dragNode.key;
+    const dropPos = info.node.pos.split('-');
+    const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
+
+    const loop = (data, key, callback) => {
+      data.forEach((item, index, arr) => {
+        if (item.key === key) {
+          callback(item, index, arr);
+          return;
+        }
+        if (item.children) {
+          loop(item.children, key, callback);
+        }
+      });
+    };
+    const data = [...this.state.gData];
+
+    // Find dragObject
+    let dragObj;
+    loop(data, dragKey, (item, index, arr) => {
+      arr.splice(index, 1);
+      dragObj = item;
+    });
+
+    if (dropPosition === 0) {
+      // Drop on the content
+      loop(data, dropKey, item => {
+        // eslint-disable-next-line no-param-reassign
+        item.children = item.children || [];
+        // where to insert 示例添加到尾部，可以是随意位置
+        item.children.unshift(dragObj);
+      });
+    } else {
+      // Drop on the gap (insert before or insert after)
+      let ar;
+      let i;
+      loop(data, dropKey, (item, index, arr) => {
+        ar = arr;
+        i = index;
+      });
+      if (dropPosition === -1) {
+        ar.splice(i, 0, dragObj);
+      } else {
+        ar.splice(i + 1, 0, dragObj);
+      }
+    }
+
+    this.setState({
+      gData: data,
+    });
+  };
+
+  onExpand = expandedKeys => {
+    console.log('onExpand', expandedKeys);
+    this.setState({
+      expandedKeys,
+      autoExpandParent: false,
+    });
+  };
+
+  render() {
+    return (
+      <div className="draggable-demo">
+        <h2>draggable with allow drop</h2>
+        <p>node can not be dropped inside a leaf node</p>
+        <div className="draggable-container">
+          <Tree
+            allowDrop={allowDrop}
+            expandedKeys={this.state.expandedKeys}
+            onExpand={this.onExpand}
+            autoExpandParent={this.state.autoExpandParent}
+            draggable
+            onDragStart={this.onDragStart}
+            onDrop={this.onDrop}
+            treeData={this.state.gData}
+          />
+        </div>
+      </div>
+    );
+  }
+}
+
+export default Demo;
diff --git a/examples/draggable.jsx b/examples/draggable.jsx
index b933d408..7ff85c2e 100644
--- a/examples/draggable.jsx
+++ b/examples/draggable.jsx
@@ -3,7 +3,7 @@ import React from 'react';
 import { gData } from './utils/dataUtil';
 import './draggable.less';
 import '../assets/index.less';
-import Tree, { TreeNode } from '../src';
+import Tree from '../src';
 
 class Demo extends React.Component {
   state = {
@@ -16,18 +16,15 @@ class Demo extends React.Component {
     console.log('start', info);
   };
 
-  onDragEnter = info => {
-    console.log('enter', info);
-    this.setState({
-      expandedKeys: info.expandedKeys,
-    });
+  onDragEnter = () => {
+    console.log('enter');
   };
 
   onDrop = info => {
     console.log('drop', info);
-    const dropKey = info.node.props.eventKey;
-    const dragKey = info.dragNode.props.eventKey;
-    const dropPos = info.node.props.pos.split('-');
+    const dropKey = info.node.key;
+    const dragKey = info.dragNode.key;
+    const dropPos = info.node.pos.split('-');
     const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
 
     const loop = (data, key, callback) => {
@@ -50,19 +47,8 @@ class Demo extends React.Component {
       dragObj = item;
     });
 
-    if (!info.dropToGap) {
+    if (dropPosition === 0) {
       // Drop on the content
-      loop(data, dropKey, item => {
-        // eslint-disable-next-line no-param-reassign
-        item.children = item.children || [];
-        // where to insert 示例添加到尾部，可以是随意位置
-        item.children.push(dragObj);
-      });
-    } else if (
-      (info.node.props.children || []).length > 0 && // Has children
-      info.node.props.expanded && // Is expanded
-      dropPosition === 1 // On the bottom gap
-    ) {
       loop(data, dropKey, item => {
         // eslint-disable-next-line no-param-reassign
         item.children = item.children || [];
@@ -70,7 +56,7 @@ class Demo extends React.Component {
         item.children.unshift(dragObj);
       });
     } else {
-      // Drop on the gap
+      // Drop on the gap (insert before or insert after)
       let ar;
       let i;
       loop(data, dropKey, (item, index, arr) => {
@@ -98,17 +84,6 @@ class Demo extends React.Component {
   };
 
   render() {
-    const loop = data =>
-      data.map(item => {
-        if (item.children && item.children.length) {
-          return (
-            <TreeNode key={item.key} title={item.title}>
-              {loop(item.children)}
-            </TreeNode>
-          );
-        }
-        return <TreeNode key={item.key} title={item.title} />;
-      });
     return (
       <div className="draggable-demo">
         <h2>draggable</h2>
@@ -122,9 +97,8 @@ class Demo extends React.Component {
             onDragStart={this.onDragStart}
             onDragEnter={this.onDragEnter}
             onDrop={this.onDrop}
-          >
-            {loop(this.state.gData)}
-          </Tree>
+            treeData={this.state.gData}
+          />
         </div>
       </div>
     );
diff --git a/package.json b/package.json
index 8b24bd92..f15908e0 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js",
     "test": "father test",
+    "coverage": "father test --coverage",
     "gh-pages": "npm run build && father doc deploy",
     "now-build": "npm run build"
   },
diff --git a/src/DropIndicator.tsx b/src/DropIndicator.tsx
new file mode 100644
index 00000000..3f519fe1
--- /dev/null
+++ b/src/DropIndicator.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react'
+
+export default function DropIndicator ({
+  dropPosition,
+  dropLevelOffset,
+  indent,
+}: {
+  dropPosition: -1 | 0 | 1,
+  dropLevelOffset: number,
+  indent: number,
+}) {
+  const style: React.CSSProperties = {
+    pointerEvents: 'none',
+    position: 'absolute',
+    right: 0,
+    backgroundColor: 'red',
+    height: 2,
+  };
+  switch (dropPosition) {
+    case -1:
+      style.top = 0;
+      style.left = -dropLevelOffset * indent;
+      break;
+    case 1:
+      style.bottom = 0;
+      style.left = -dropLevelOffset * indent;
+      break;
+    case 0:
+      style.bottom = 0;
+      style.left = indent;
+      break;
+  }
+  return <div style={style} />;
+}
diff --git a/src/Indent.tsx b/src/Indent.tsx
index 11f4cfcc..3cce26c2 100644
--- a/src/Indent.tsx
+++ b/src/Indent.tsx
@@ -8,11 +8,7 @@ interface IndentProps {
   isEnd: boolean[];
 }
 
-const Indent: React.FC<IndentProps> = ({ prefixCls, level, isStart, isEnd }) => {
-  if (!level) {
-    return null;
-  }
-
+const Indent = ({ prefixCls, level, isStart, isEnd }: IndentProps) => {
   const baseClassName = `${prefixCls}-indent-unit`;
   const list: React.ReactElement[] = [];
   for (let i = 0; i < level; i += 1) {
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 0e6880d2..ca4a014b 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -48,6 +48,7 @@ const MotionFlattenData: FlattenNode = {
 
 export interface NodeListRef {
   scrollTo: ScrollTo;
+  getIndentWidth: () => number;
 }
 
 interface NodeListProps {
@@ -167,10 +168,12 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
 
   // =============================== Ref ================================
   const listRef = React.useRef<ListRef>(null);
+  const indentMeasurerRef = React.useRef<HTMLDivElement>(null);
   React.useImperativeHandle(ref, () => ({
     scrollTo: scroll => {
       listRef.current.scrollTo(scroll);
     },
+    getIndentWidth: () => indentMeasurerRef.current.offsetWidth,
   }));
 
   // ============================== Motion ==============================
@@ -278,6 +281,22 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         />
       </div>
 
+      <div
+        className={`${prefixCls}-treenode`}
+        aria-hidden
+        style={{
+          position: 'absolute',
+          pointerEvents: 'none',
+          visibility: 'hidden',
+          height: 0,
+          overflow: 'hidden',
+        }}
+      >
+        <div className={`${prefixCls}-indent`}>
+          <div ref={indentMeasurerRef} className={`${prefixCls}-indent-unit`} />
+        </div>
+      </div>
+
       <VirtualList<FlattenNode>
         {...domProps}
         data={mergedData}
@@ -305,7 +324,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
             <MotionTreeNode
               {...restProps}
               {...treeNodeProps}
-              active={activeItem && key === activeItem.data.key}
+              active={!!activeItem && key === activeItem.data.key}
               pos={pos}
               data={treeNode.data}
               isStart={isStart}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 76cad714..f7beca75 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -15,7 +15,7 @@ import {
 } from './contextTypes';
 import {
   getDataAndAria,
-  getDragNodesKeys,
+  getDragChildrenKeys,
   parseCheckedKeys,
   conductExpandParent,
   calcSelectedKeys,
@@ -45,6 +45,7 @@ import {
 import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
 import TreeNode from './TreeNode';
 import { conductCheck } from './utils/conductUtil';
+import DropIndicator from './DropIndicator';
 
 interface CheckInfo {
   event: 'check';
@@ -56,6 +57,8 @@ interface CheckInfo {
   halfCheckedKeys?: Key[];
 }
 
+export type AllowDrop = (options: { dropNode: DataNode; dropPosition: -1 | 0 | 1 }) => boolean;
+
 export interface TreeProps {
   prefixCls: string;
   className?: string;
@@ -72,7 +75,7 @@ export interface TreeProps {
   multiple?: boolean;
   checkable?: boolean | React.ReactNode;
   checkStrictly?: boolean;
-  draggable?: boolean;
+  draggable?: ((node: DataNode) => boolean) | boolean;
   defaultExpandParent?: boolean;
   autoExpandParent?: boolean;
   defaultExpandAll?: boolean;
@@ -82,7 +85,14 @@ export interface TreeProps {
   checkedKeys?: Key[] | { checked: Key[]; halfChecked: Key[] };
   defaultSelectedKeys?: Key[];
   selectedKeys?: Key[];
+  allowDrop?: AllowDrop;
   titleRender?: (node: DataNode) => React.ReactNode;
+  dropIndicatorRender?: (props: {
+    dropPosition: -1 | 0 | 1;
+    dropLevelOffset: number;
+    indent: number;
+    prefixCls: string;
+  }) => React.ReactNode;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
@@ -151,6 +161,8 @@ export interface TreeProps {
 interface TreeState {
   keyEntities: Record<Key, DataEntity>;
 
+  indent: number | null;
+
   selectedKeys: Key[];
   checkedKeys: Key[];
   halfCheckedKeys: Key[];
@@ -159,9 +171,16 @@ interface TreeState {
   expandedKeys: Key[];
 
   dragging: boolean;
-  dragNodesKeys: Key[];
-  dragOverNodeKey: Key;
-  dropPosition: number;
+  dragChildrenKeys: Key[];
+
+  // for details see comment in Tree.state
+  dropPosition: -1 | 0 | 1 | null;
+  dropLevelOffset: number | null;
+  dropContainerKey: Key | null;
+  dropTargetKey: Key | null;
+  dropTargetPos: string | null;
+  dropAllowed: boolean;
+  dragOverNodeKey: Key | null;
 
   treeData: DataNode[];
   flattenNodes: FlattenNode[];
@@ -192,6 +211,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
     defaultExpandedKeys: [],
     defaultCheckedKeys: [],
     defaultSelectedKeys: [],
+    dropIndicatorRender: DropIndicator,
+    allowDrop: () => true,
   };
 
   static TreeNode = TreeNode;
@@ -203,6 +224,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
   state: TreeState = {
     keyEntities: {},
 
+    indent: null,
+
     selectedKeys: [],
     checkedKeys: [],
     halfCheckedKeys: [],
@@ -211,9 +234,21 @@ class Tree extends React.Component<TreeProps, TreeState> {
     expandedKeys: [],
 
     dragging: false,
-    dragNodesKeys: [],
+    dragChildrenKeys: [],
+
+    // dropTargetKey is the key of abstract-drop-node
+    // the abstract-drop-node is the real drop node when drag and drop
+    // not the DOM drag over node
+    dropTargetKey: null,
+    dropPosition: null, // the drop position of abstract-drop-node, inside 0, top -1, bottom 1
+    dropContainerKey: null, // the container key of abstract-drop-node if dropPosition is -1 or 1
+    dropLevelOffset: null, // the drop level offset of abstract-drag-over-node
+    dropTargetPos: null, // the pos of abstract-drop-node
+    dropAllowed: true, // if drop to abstract-drop-node is allowed
+    // the abstract-drag-over-node
+    // if mouse is on the bottom of top dom node or no the top of the bottom dom node
+    // abstract-drag-over-node is the top node
     dragOverNodeKey: null,
-    dropPosition: null,
 
     treeData: [],
     flattenNodes: [],
@@ -226,10 +261,17 @@ class Tree extends React.Component<TreeProps, TreeState> {
     prevProps: null,
   };
 
+  dragStartMousePosition = null;
+
   dragNode: NodeInstance;
 
   listRef = React.createRef<NodeListRef>();
 
+  componentWillUnmount() {
+    window.removeEventListener('dragend', this.onWindowDragEnd);
+    this.destroyed = true;
+  }
+
   static getDerivedStateFromProps(props: TreeProps, prevState: TreeState) {
     const { prevProps } = prevState;
     const newState: Partial<TreeState> = {
@@ -344,117 +386,246 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return newState;
   }
 
-  componentWillUnmount() {
-    this.destroyed = true;
-  }
-
   onNodeDragStart: NodeDragEventHandler = (event, node) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
     const { eventKey } = node.props;
 
     this.dragNode = node;
+    this.dragStartMousePosition = {
+      x: event.clientX,
+      y: event.clientY,
+    };
 
     const newExpandedKeys = arrDel(expandedKeys, eventKey);
 
     this.setState({
       dragging: true,
-      dragNodesKeys: getDragNodesKeys(eventKey, keyEntities),
+      dragChildrenKeys: getDragChildrenKeys(eventKey, keyEntities),
+      indent: this.listRef.current.getIndentWidth(),
     });
 
     this.setExpandedKeys(newExpandedKeys);
 
+    window.addEventListener('dragend', this.onWindowDragEnd);
+
     if (onDragStart) {
       onDragStart({ event, node: convertNodePropsToEventData(node.props) });
     }
   };
 
   /**
-   * [Legacy] Select handler is less small than node,
+   * [Legacy] Select handler is smaller than node,
    * so that this will trigger when drag enter node or select handler.
    * This is a little tricky if customize css without padding.
    * Better for use mouse move event to refresh drag state.
    * But let's just keep it to avoid event trigger logic change.
    */
-  onNodeDragEnter: NodeDragEventHandler = (event, node) => {
-    const { expandedKeys, keyEntities, dragNodesKeys } = this.state;
-    const { onDragEnter } = this.props;
-    const { pos, eventKey } = node.props;
-
-    if (!this.dragNode || dragNodesKeys.indexOf(eventKey) !== -1) return;
+  onNodeDragEnter = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+    const {
+      expandedKeys,
+      keyEntities,
+      dragChildrenKeys,
+      flattenNodes,
+      indent,
+    } = this.state;
+    const { onDragEnter, onExpand, allowDrop } = this.props;
+    const { pos } = node.props;
+    const { dragNode } = this;
 
-    const dropPosition = calcDropPosition(event, node);
+    const {
+      dropPosition,
+      dropLevelOffset,
+      dropTargetKey,
+      dropContainerKey,
+      dropTargetPos,
+      dropAllowed,
+      dragOverNodeKey,
+    } = calcDropPosition(
+      event,
+      node,
+      indent,
+      this.dragStartMousePosition,
+      allowDrop,
+      flattenNodes,
+      keyEntities,
+      expandedKeys,
+    );
 
-    // Skip if drag node is self
-    if (this.dragNode.props.eventKey === eventKey && dropPosition === 0) {
+    if (
+      !dragNode ||
+      // don't allow drop inside its children
+      dragChildrenKeys.indexOf(dropTargetKey) !== -1 ||
+      // don't allow drop when drop is not allowed caculated by calcDropPosition
+      !dropAllowed
+    ) {
       this.setState({
-        dragOverNodeKey: '',
+        dragOverNodeKey: null,
         dropPosition: null,
+        dropLevelOffset: null,
+        dropTargetKey: null,
+        dropContainerKey: null,
+        dropTargetPos: null,
+        dropAllowed: false,
       });
       return;
     }
 
-    // Ref: https://github.com/react-component/tree/issues/132
-    // Add timeout to let onDragLevel fire before onDragEnter,
-    // so that we can clean drag props for onDragLeave node.
-    // Macro task for this:
-    // https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script
-    setTimeout(() => {
-      // Update drag over node
-      this.setState({
-        dragOverNodeKey: eventKey,
-        dropPosition,
-      });
+    // Side effect for delay drag
+    if (!this.delayedDragEnterLogic) {
+      this.delayedDragEnterLogic = {};
+    }
+    Object.keys(this.delayedDragEnterLogic).forEach(key => {
+      clearTimeout(this.delayedDragEnterLogic[key]);
+    });
 
-      // Side effect for delay drag
-      if (!this.delayedDragEnterLogic) {
-        this.delayedDragEnterLogic = {};
-      }
-      Object.keys(this.delayedDragEnterLogic).forEach(key => {
-        clearTimeout(this.delayedDragEnterLogic[key]);
-      });
+    if (dragNode.props.eventKey !== node.props.eventKey) {
+      // hoist expand logic here
+      // since if logic is on the bottom
+      // it will be blocked by abstract dragover node check
+      //   => if you dragenter from top, you mouse will still be consider as in the top node
+      event.persist();
       this.delayedDragEnterLogic[pos] = window.setTimeout(() => {
         if (!this.state.dragging) return;
 
         let newExpandedKeys = [...expandedKeys];
-        const entity = keyEntities[eventKey];
+        const entity = keyEntities[node.props.eventKey];
 
         if (entity && (entity.children || []).length) {
-          newExpandedKeys = arrAdd(expandedKeys, eventKey);
+          newExpandedKeys = arrAdd(expandedKeys, node.props.eventKey);
         }
 
         if (!('expandedKeys' in this.props)) {
           this.setExpandedKeys(newExpandedKeys);
         }
 
-        if (onDragEnter) {
-          onDragEnter({
-            event,
+        if (onExpand) {
+          onExpand(newExpandedKeys, {
             node: convertNodePropsToEventData(node.props),
-            expandedKeys: newExpandedKeys,
+            expanded: true,
+            nativeEvent: event.nativeEvent,
           });
         }
-      }, 400);
-    }, 0);
+      }, 800);
+    }
+
+    // Skip if drag node is self
+    if (dragNode.props.eventKey === dropTargetKey && dropLevelOffset === 0) {
+      this.setState({
+        dragOverNodeKey: null,
+        dropPosition: null,
+        dropLevelOffset: null,
+        dropTargetKey: null,
+        dropContainerKey: null,
+        dropTargetPos: null,
+        dropAllowed: false,
+      });
+      return;
+    }
+
+    // Update drag over node and drag state
+    this.setState({
+      dragOverNodeKey,
+      dropPosition,
+      dropLevelOffset,
+      dropTargetKey,
+      dropContainerKey,
+      dropTargetPos,
+      dropAllowed,
+    });
+
+    if (onDragEnter) {
+      onDragEnter({
+        event,
+        node: convertNodePropsToEventData(node.props),
+        expandedKeys,
+      });
+    }
   };
 
-  onNodeDragOver: NodeDragEventHandler = (event, node) => {
-    const { dragNodesKeys } = this.state;
-    const { onDragOver } = this.props;
-    const { eventKey } = node.props;
+  onNodeDragOver = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+    const {
+      dragChildrenKeys,
+      flattenNodes,
+      keyEntities,
+      expandedKeys,
+      indent,
+    } = this.state;
+    const { onDragOver, allowDrop } = this.props;
+    const { dragNode } = this;
 
-    if (dragNodesKeys.indexOf(eventKey) !== -1) {
+    const {
+      dropPosition,
+      dropLevelOffset,
+      dropTargetKey,
+      dropContainerKey,
+      dropAllowed,
+      dropTargetPos,
+      dragOverNodeKey,
+    } = calcDropPosition(
+      event,
+      node,
+      indent,
+      this.dragStartMousePosition,
+      allowDrop,
+      flattenNodes,
+      keyEntities,
+      expandedKeys,
+    );
+
+    if (
+      !dragNode ||
+      dragChildrenKeys.indexOf(dropTargetKey) !== -1 ||
+      !dropAllowed
+    ) {
+      // don't allow drop inside its children
+      // don't allow drop when drop is not allowed caculated by calcDropPosition
       return;
     }
 
     // Update drag position
-    if (this.dragNode && eventKey === this.state.dragOverNodeKey) {
-      const dropPosition = calcDropPosition(event, node);
-
-      if (dropPosition === this.state.dropPosition) return;
 
+    if (dragNode.props.eventKey === dropTargetKey && dropLevelOffset === 0) {
+      if (
+        !(
+          this.state.dropPosition === null &&
+          this.state.dropLevelOffset === null &&
+          this.state.dropTargetKey === null &&
+          this.state.dropContainerKey === null &&
+          this.state.dropTargetPos === null &&
+          this.state.dropAllowed === false &&
+          this.state.dragOverNodeKey === null
+        )
+      ) {
+        this.setState({
+          dropPosition: null,
+          dropLevelOffset: null,
+          dropTargetKey: null,
+          dropContainerKey: null,
+          dropTargetPos: null,
+          dropAllowed: false,
+          dragOverNodeKey: null,
+        });
+      }
+    } else if (
+      !(
+        dropPosition === this.state.dropPosition &&
+        dropLevelOffset === this.state.dropLevelOffset &&
+        dropTargetKey === this.state.dropTargetKey &&
+        dropContainerKey === this.state.dropContainerKey &&
+        dropTargetPos === this.state.dropTargetPos &&
+        dropAllowed === this.state.dropAllowed &&
+        dragOverNodeKey === this.state.dragOverNodeKey
+      )
+    ) {
       this.setState({
         dropPosition,
+        dropLevelOffset,
+        dropTargetKey,
+        dropContainerKey,
+        dropTargetPos,
+        dropAllowed,
+        dragOverNodeKey,
       });
     }
 
@@ -466,60 +637,81 @@ class Tree extends React.Component<TreeProps, TreeState> {
   onNodeDragLeave: NodeDragEventHandler = (event, node) => {
     const { onDragLeave } = this.props;
 
-    this.setState({
-      dragOverNodeKey: '',
-    });
-
     if (onDragLeave) {
       onDragLeave({ event, node: convertNodePropsToEventData(node.props) });
     }
   };
 
-  onNodeDragEnd: NodeDragEventHandler = (event, node) => {
+  // since stopPropagation() is called in treeNode
+  // if onWindowDrag is called, whice means state is keeped, drag state should be cleared
+  onWindowDragEnd = event => {
+    this.onNodeDragEnd(event, null, true);
+    window.removeEventListener('dragend', this.onWindowDragEnd);
+  };
+
+  // if onNodeDragEnd is called, onWindowDragEnd won't be called since stopPropagation() is called
+  onNodeDragEnd: NodeDragEventHandler = (event, node, outsideTree = false) => {
     const { onDragEnd } = this.props;
     this.setState({
-      dragOverNodeKey: '',
+      dragOverNodeKey: null,
     });
+
     this.cleanDragState();
 
-    if (onDragEnd) {
+    if (onDragEnd && !outsideTree) {
       onDragEnd({ event, node: convertNodePropsToEventData(node.props) });
     }
 
     this.dragNode = null;
   };
 
-  onNodeDrop: NodeDragEventHandler = (event, node) => {
-    const { dragNodesKeys = [], dropPosition } = this.state;
+  onNodeDrop = (event: React.MouseEvent<HTMLDivElement>, node, outsideTree: boolean = false) => {
+    const {
+      dragChildrenKeys,
+      dropPosition,
+      dropTargetKey,
+      dropTargetPos,
+      dropAllowed,
+    } = this.state;
+
+    if (!dropAllowed) return;
+
     const { onDrop } = this.props;
-    const { eventKey, pos } = node.props;
 
     this.setState({
-      dragOverNodeKey: '',
+      dragOverNodeKey: null,
     });
     this.cleanDragState();
 
-    if (dragNodesKeys.indexOf(eventKey) !== -1) {
-      warning(false, "Can not drop to dragNode(include it's children node)");
-      return;
+    if (dropTargetKey === null) return;
+
+    const abstractDropNodeProps = {
+      ...getTreeNodeProps(
+        dropTargetKey,
+        this.getTreeNodeRequiredProps(),
+      ),
+      active: this.getActiveItem()?.data.key === dropTargetKey,
+      data: this.state.keyEntities[dropTargetKey].node,
     }
+    const dropToChild = dragChildrenKeys.indexOf(dropTargetKey) !== -1;
 
-    const posArr = posToArr(pos);
+    warning(
+      !dropToChild,
+      "Can not drop to dragNode's children node. This is a bug of rc-tree. Please report an issue.",
+    );
+
+    const posArr = posToArr(dropTargetPos);
 
     const dropResult = {
       event,
-      node: convertNodePropsToEventData(node.props),
+      node: convertNodePropsToEventData(abstractDropNodeProps),
       dragNode: this.dragNode ? convertNodePropsToEventData(this.dragNode.props) : null,
-      dragNodesKeys: dragNodesKeys.slice(),
+      dragNodesKeys: [this.dragNode.props.eventKey].concat(dragChildrenKeys),
+      dropToGap: dropPosition !== 0,
       dropPosition: dropPosition + Number(posArr[posArr.length - 1]),
-      dropToGap: false,
     };
 
-    if (dropPosition !== 0) {
-      dropResult.dropToGap = true;
-    }
-
-    if (onDrop) {
+    if (onDrop && !outsideTree) {
       onDrop(dropResult);
     }
 
@@ -531,8 +723,15 @@ class Tree extends React.Component<TreeProps, TreeState> {
     if (dragging) {
       this.setState({
         dragging: false,
+        dropPosition: null,
+        dropContainerKey: null,
+        dropTargetKey: null,
+        dropLevelOffset: null,
+        dropAllowed: true,
+        dragOverNodeKey: null,
       });
     }
+    this.dragStartMousePosition = null;
   };
 
   onNodeClick: NodeMouseEventHandler = (e, treeNode) => {
@@ -1035,7 +1234,19 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   render() {
-    const { focused, flattenNodes, keyEntities, dragging, activeKey } = this.state;
+    const {
+      focused,
+      flattenNodes,
+      keyEntities,
+      dragging,
+      activeKey,
+      dropLevelOffset,
+      dropContainerKey,
+      dropTargetKey,
+      dropPosition,
+      dragOverNodeKey,
+      indent,
+    } = this.state;
     const {
       prefixCls,
       className,
@@ -1058,6 +1269,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       itemHeight,
       virtual,
       titleRender,
+      dropIndicatorRender,
       onContextMenu,
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
@@ -1075,6 +1287,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
           checkStrictly,
           disabled,
           keyEntities,
+          dropLevelOffset,
+          dropContainerKey,
+          dropTargetKey,
+          dropPosition,
+          dragOverNodeKey,
+          indent,
+          dropIndicatorRender,
 
           loadData,
           filterTreeNode,
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 8e434028..c3b4df7e 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -401,6 +401,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       context: { prefixCls, showIcon, icon: treeIcon, draggable, loadData, titleRender },
     } = this.props;
     const disabled = this.isDisabled();
+    const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
 
     const wrapClass = `${prefixCls}-node-content-wrapper`;
 
@@ -441,23 +442,45 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           `${wrapClass}`,
           `${wrapClass}-${this.getNodeState() || 'normal'}`,
           !disabled && (selected || dragNodeHighlight) && `${prefixCls}-node-selected`,
-          !disabled && draggable && 'draggable',
+          !disabled && mergedDraggable && 'draggable',
         )}
-        draggable={(!disabled && draggable) || undefined}
-        aria-grabbed={(!disabled && draggable) || undefined}
+        draggable={(!disabled && mergedDraggable) || undefined}
+        aria-grabbed={(!disabled && mergedDraggable) || undefined}
         onMouseEnter={this.onMouseEnter}
         onMouseLeave={this.onMouseLeave}
         onContextMenu={this.onContextMenu}
         onClick={this.onSelectorClick}
         onDoubleClick={this.onSelectorDoubleClick}
-        onDragStart={draggable ? this.onDragStart : undefined}
+        onDragStart={mergedDraggable ? this.onDragStart : undefined}
       >
         {$icon}
         {$title}
+        {this.renderDropIndicator()}
       </span>
     );
   };
 
+  renderDropIndicator = () => {
+    const { disabled, eventKey } = this.props;
+    const {
+      context: {
+        draggable,
+        dropLevelOffset,
+        dropPosition,
+        prefixCls,
+        indent,
+        dropIndicatorRender,
+        dragOverNodeKey,
+      },
+    } = this.props;
+    const mergedDraggable = draggable !== false;
+    // allowDrop is calculated in Tree.tsx, there is no need for calc it here
+    const showIndicator = !disabled && mergedDraggable && dragOverNodeKey === eventKey;
+    return showIndicator
+      ? dropIndicatorRender({ dropPosition, dropLevelOffset, indent, prefixCls })
+      : null;
+  };
+
   render() {
     const {
       eventKey,
@@ -476,16 +499,25 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       loading,
       domRef,
       active,
+      data,
       onMouseMove,
       ...otherProps
     } = this.props;
     const {
-      context: { prefixCls, filterTreeNode, draggable, keyEntities },
+      context: {
+        prefixCls,
+        filterTreeNode,
+        draggable,
+        keyEntities,
+        dropContainerKey,
+        dropTargetKey,
+      },
     } = this.props;
     const disabled = this.isDisabled();
     const dataOrAriaAttributeProps = getDataAndAria(otherProps);
     const { level } = keyEntities[eventKey] || {};
     const isEndNode = isEnd[isEnd.length - 1];
+    const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
     return (
       <div
         ref={domRef}
@@ -499,17 +531,19 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           [`${prefixCls}-treenode-active`]: active,
           [`${prefixCls}-treenode-leaf-last`]: isEndNode,
 
+          'drop-target': dropTargetKey === eventKey,
+          'drop-container': dropContainerKey === eventKey,
           'drag-over': !disabled && dragOver,
           'drag-over-gap-top': !disabled && dragOverGapTop,
           'drag-over-gap-bottom': !disabled && dragOverGapBottom,
           'filter-node': filterTreeNode && filterTreeNode(convertNodePropsToEventData(this.props)),
         })}
         style={style}
-        onDragEnter={draggable ? this.onDragEnter : undefined}
-        onDragOver={draggable ? this.onDragOver : undefined}
-        onDragLeave={draggable ? this.onDragLeave : undefined}
-        onDrop={draggable ? this.onDrop : undefined}
-        onDragEnd={draggable ? this.onDragEnd : undefined}
+        onDragEnter={mergedDraggable ? this.onDragEnter : undefined}
+        onDragOver={mergedDraggable ? this.onDragOver : undefined}
+        onDragLeave={mergedDraggable ? this.onDragLeave : undefined}
+        onDrop={mergedDraggable ? this.onDrop : undefined}
+        onDragEnd={mergedDraggable ? this.onDragEnd : undefined}
         onMouseMove={onMouseMove}
         {...dataOrAriaAttributeProps}
       >
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index f880b01b..ce2e5ae2 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -21,6 +21,7 @@ export type NodeMouseEventHandler<T = HTMLSpanElement> = (
 export type NodeDragEventHandler<T = HTMLDivElement> = (
   e: React.MouseEvent<T>,
   node: NodeInstance,
+  outsideTree?: boolean,
 ) => void;
 
 export interface TreeContextProps {
@@ -29,11 +30,24 @@ export interface TreeContextProps {
   showIcon: boolean;
   icon: IconType;
   switcherIcon: IconType;
-  draggable: boolean;
+  draggable: ((node: DataNode) => boolean) | boolean;
   checkable: boolean | React.ReactNode;
   checkStrictly: boolean;
   disabled: boolean;
   keyEntities: Record<Key, DataEntity>;
+  // for details see comment in Tree.state (Tree.tsx)
+  dropLevelOffset?: number;
+  dropContainerKey: Key | null;
+  dropTargetKey: Key | null;
+  dropPosition: -1 | 0 | 1 | null;
+  indent: number | null;
+  dropIndicatorRender: (props: {
+    dropPosition: -1 | 0 | 1;
+    dropLevelOffset: number;
+    indent;
+    prefixCls;
+  }) => React.ReactNode;
+  dragOverNodeKey: Key | null;
 
   loadData: (treeNode: EventDataNode) => Promise<void>;
   filterTreeNode: (treeNode: EventDataNode) => boolean;
diff --git a/src/util.tsx b/src/util.tsx
index 227a5ec6..3c5c0d1c 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable no-lonely-if */
 /**
  * Legacy code. Should avoid to use if you are new to import these code.
  */
@@ -5,11 +6,8 @@
 import React from 'react';
 import warning from 'rc-util/lib/warning';
 import TreeNode, { TreeNodeProps } from './TreeNode';
-import { NodeElement, Key, DataNode, Entity, DataEntity, NodeInstance } from './interface';
-import { TreeProps } from './Tree';
-
-const DRAG_SIDE_RANGE = 0.25;
-const DRAG_MIN_GAP = 2;
+import { NodeElement, Key, DataNode, Entity, DataEntity, NodeInstance, FlattenNode } from './interface';
+import { TreeProps, AllowDrop } from './Tree';
 
 export function arrDel(list: Key[], value: Key) {
   const clone = list.slice();
@@ -40,36 +38,185 @@ export function isTreeNode(node: NodeElement) {
   return node && node.type && node.type.isTreeNode;
 }
 
-export function getDragNodesKeys(dragNodeKey: Key, keyEntities: Record<Key, DataEntity>): Key[] {
-  const dragNodesKeys = [dragNodeKey];
+export function getDragChildrenKeys(dragNodeKey: Key, keyEntities: Record<Key, DataEntity>): Key[] {
+  // not contains self
+  // self for left or right drag
+  const dragChildrenKeys = [];
 
   const entity = keyEntities[dragNodeKey];
   function dig(list: DataEntity[] = []) {
     list.forEach(({ key, children }) => {
-      dragNodesKeys.push(key);
+      dragChildrenKeys.push(key);
       dig(children);
     });
   }
 
   dig(entity.children);
 
-  return dragNodesKeys;
+  return dragChildrenKeys;
+}
+
+export function isLastChild (treeNodeEntity: DataEntity) {
+  if (treeNodeEntity.parent) {
+    const posArr = posToArr(treeNodeEntity.pos);
+    return Number(posArr[posArr.length - 1]) === treeNodeEntity.parent.children.length - 1;
+  }
+  return false;
+}
+
+export function isFirstChild (treeNodeEntity: DataEntity) {
+  const posArr = posToArr(treeNodeEntity.pos);
+  return Number(posArr[posArr.length - 1]) === 0;
 }
 
 // Only used when drag, not affect SSR.
-export function calcDropPosition(event: React.MouseEvent, treeNode: NodeInstance) {
-  const { clientY } = event;
-  const { top, bottom, height } = treeNode.selectHandle.getBoundingClientRect();
-  const des = Math.max(height * DRAG_SIDE_RANGE, DRAG_MIN_GAP);
+export function calcDropPosition(
+  event: React.MouseEvent,
+  targetNode: NodeInstance,
+  indent: number,
+  startMousePosition: {
+    x: number,
+    y: number,
+  },
+  allowDrop: AllowDrop,
+  flattenedNodes: FlattenNode[],
+  keyEntities: Record<Key, DataEntity>,
+  expandKeys: Key[],
+) : {
+  dropPosition: -1 | 0 | 1,
+  dropLevelOffset: number,
+  dropTargetKey: Key,
+  dropTargetPos: string,
+  dropContainerKey: Key,
+  dragOverNodeKey: Key,
+  dropAllowed: boolean,
+} {
+  const { clientX, clientY } = event;
+  const { top, height } = (event.target as HTMLElement).getBoundingClientRect();
+  // optional chain for testing
+  const horizontalMouseOffset = (startMousePosition?.x || 0) - clientX;
+  const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent;
+
+  // find abstract drop node by horizontal offset
+  let abstractDropNodeEntity: DataEntity = keyEntities[targetNode.props.eventKey];
+
+  if (clientY < top + height / 2) {
+    // first half, set abstract drop node to previous node
+    const nodeIndex = flattenedNodes.findIndex(
+      flattenedNode => flattenedNode.data.key === abstractDropNodeEntity.key,
+    );
+    const prevNodeIndex = nodeIndex <= 0 ? 0 : nodeIndex - 1;
+    const prevNodeKey = flattenedNodes[prevNodeIndex].data.key;
+    abstractDropNodeEntity = keyEntities[prevNodeKey];
+  }
 
-  if (clientY <= top + des) {
-    return -1;
+  const abstractDragOverEntity = abstractDropNodeEntity;
+  const dragOverNodeKey = abstractDropNodeEntity.key;
+
+  let dropPosition: -1 | 0 | 1 = 0;
+  let dropLevelOffset = 0;
+  for (let i = 0; i < rawDropLevelOffset; i += 1) {
+    if (
+      isLastChild(abstractDropNodeEntity)
+    ) {
+      abstractDropNodeEntity = abstractDropNodeEntity.parent;
+      dropLevelOffset += 1;
+    } else {
+      break;
+    }
   }
-  if (clientY >= bottom - des) {
-    return 1;
+
+  const abstractDropDataNode = abstractDropNodeEntity.node
+  let dropAllowed = true;
+  if (
+    isFirstChild(abstractDropNodeEntity) &&
+    abstractDropNodeEntity.level === 0 &&
+    clientY < top + height / 2 &&
+    allowDrop({
+      dropNode: abstractDropDataNode,
+      dropPosition: -1,
+    }) &&
+    abstractDropNodeEntity.key === targetNode.props.eventKey
+  ) {
+    // first half of first node in first level
+    dropPosition = -1
+  } else if (
+    (abstractDragOverEntity.children || []).length &&
+    expandKeys.includes(dragOverNodeKey)
+  ) {
+    // drop on expanded node
+    // only allow drop inside
+    if (allowDrop({
+      dropNode: abstractDropDataNode,
+      dropPosition: 0,
+    })) {
+      dropPosition = 0;
+    } else {
+      dropAllowed = false
+    }
+  } else if (
+    dropLevelOffset === 0
+  ) {
+    if (rawDropLevelOffset > -1.5) {
+      // | Node     | <- abstractDropNode
+      // | -^-===== | <- mousePosition
+      // 1. try drop after
+      // 2. do not allow drop
+      if (allowDrop({
+        dropNode: abstractDropDataNode,
+        dropPosition: 1,
+      })) {
+        dropPosition = 1;
+      } else {
+        dropAllowed = false;
+      }
+    } else {
+      // | Node     | <- abstractDropNode
+      // | ---==^== | <- mousePosition
+      // whether it has children or doesn't has children
+      // always
+      // 1. try drop inside
+      // 2. try drop after
+      // 3. do not allow drop
+      if (allowDrop({
+        dropNode: abstractDropDataNode,
+        dropPosition: 0,
+      })) {
+        dropPosition = 0;
+      } else if (allowDrop({
+        dropNode: abstractDropDataNode,
+        dropPosition: 1,
+      })) {
+        dropPosition = 1;
+      } else {
+        dropAllowed = false;
+      }
+    }
+  } else {
+    // | Node1 | <- abstractDropNode
+    //      |  Node2  |
+    // --^--|----=====| <- mousePosition
+    // 1. try insert after Node1
+    // 2. do not allow drop
+    if (allowDrop({
+      dropNode: abstractDropDataNode,
+      dropPosition: 1,
+    })) {
+      dropPosition = 1;
+    } else {
+      dropAllowed = false;
+    }
   }
 
-  return 0;
+  return {
+    dropPosition,
+    dropLevelOffset,
+    dropTargetKey: abstractDropNodeEntity.key,
+    dropTargetPos: abstractDropNodeEntity.pos,
+    dragOverNodeKey,
+    dropContainerKey: dropPosition === 0 ? null : (abstractDropNodeEntity.parent?.key || null),
+    dropAllowed,
+  };
 }
 
 /**
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 980132c6..6b066dec 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -301,6 +301,8 @@ export function getTreeNodeProps(
     pos: String(entity ? entity.pos : ''),
 
     // [Legacy] Drag props
+    // Since the interaction of drag is changed, the semantic of the props are
+    // not accuracy, I think it should be finally removed
     dragOver: dragOverNodeKey === key && dropPosition === 0,
     dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
     dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1,
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index 91e71bff..efcb4d4e 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -207,7 +207,10 @@ describe('Tree Accessibility', () => {
       expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeTruthy();
 
       // Mouse move
-      wrapper.find('.rc-tree-treenode').simulate('mouseMove');
+      wrapper
+        .find('.rc-tree-treenode')
+        .at(1)
+        .simulate('mouseMove');
       expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeFalsy();
     });
   });
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 5842c225..1c6b31f3 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1054,16 +1054,16 @@ describe('Tree Basic', () => {
 
       // Not trigger self
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
-      await delay(500);
+      await delay(900);
       expect(onDragEnter).not.toHaveBeenCalled();
 
       wrapper
         .find('.dropTarget')
         .at(0)
         .simulate('dragEnter');
-      expect(onDragEnter).not.toHaveBeenCalled();
+      expect(onDragEnter).toHaveBeenCalled();
 
-      await delay(500);
+      await delay(900);
       wrapper.update();
       const node = convertNodePropsToEventData(
         wrapper
@@ -1072,7 +1072,7 @@ describe('Tree Basic', () => {
           .props(),
       );
       const event = onDragEnter.mock.calls[0][0];
-      expect(event.node).toEqual(node);
+      expect(event.node.key).toEqual(node.key);
       expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
       expect(onDragEnter).toHaveBeenCalledTimes(1);
     });
@@ -1086,11 +1086,11 @@ describe('Tree Basic', () => {
           .at(2)
           .props(),
       );
+      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
       wrapper
         .find('.dropTarget')
         .at(0)
         .simulate('dragOver');
-
       const event = onDragOver.mock.calls[0][0];
       expect(event.node).toEqual(node);
     });
@@ -1116,6 +1116,8 @@ describe('Tree Basic', () => {
       const onDrop = jest.fn();
       const wrapper = mount(createTree({ onDrop }));
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+      wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
+      wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
       const dropNode = convertNodePropsToEventData(
         wrapper
           .find(InternalTreeNode)
@@ -1182,13 +1184,16 @@ describe('Tree Basic', () => {
       function dropTarget(targetSelector) {
         return new Promise(resolve => {
           const wrapper = mount(
-            <Tree draggable defaultExpandAll>
+            <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
               <TreeNode key="0-0" className="dragTarget">
                 <TreeNode key="0-0-0" className="dragTargetChild" />
               </TreeNode>
-              <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1">
                 <TreeNode key="0-1-0" />
               </TreeNode>
+              <TreeNode key="0-2" className="dropTarget">
+                <TreeNode key="0-2-0" />
+              </TreeNode>
             </Tree>,
           );
 
@@ -1206,37 +1211,474 @@ describe('Tree Basic', () => {
             wrapper.find(targetSelector).simulate('dragEnter', { clientY: 0 });
             setTimeout(() => {
               wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
-
               // 4. Drop
               wrapper.find(targetSelector).simulate('drop');
               wrapper.find('div.dragTarget').simulate('dragEnd');
 
               resolve();
-            }, 500);
+            }, 1000);
           }, 10);
         });
       }
-
-      const { getBoundingClientRect } = Element.prototype;
+      let domSpy;
       beforeEach(() => {
-        Element.prototype.getBoundingClientRect = jest.fn(() => ({
-          width: 100,
-          height: 20,
-          top: 0,
-          left: 0,
-          bottom: 20,
-          right: 100,
-        }));
+        domSpy = spyElementPrototypes(HTMLElement, {
+          offsetWidth: {
+            get() {
+              return 24;
+            },
+          },
+          getBoundingClientRect: jest.fn(() => ({
+            width: 100,
+            height: 20,
+            top: 0,
+            left: 0,
+            bottom: 20,
+            right: 100,
+          })),
+        });
+        // Object.defineProperties(window.HTMLElement.prototype, {
+        //   // mock indent as 24
+        //   // no need for clearing it, since jest make each file a independent env
+        //   offsetWidth: {
+        //     get() {
+        //       return 24;
+        //     },
+        //   },
+        // });
       });
 
       afterEach(() => {
-        Element.prototype.getBoundingClientRect = getBoundingClientRect;
+        domSpy.mockRestore();
       });
 
       it('self', () => dropTarget('div.dragTarget'));
 
       it('target', () => dropTarget('div.dropTarget'));
     });
+
+    describe('new drop logic', () => {
+      let domSpy;
+      beforeEach(() => {
+        domSpy = spyElementPrototypes(HTMLElement, {
+          getBoundingClientRect: () => ({
+            width: 100,
+            height: 20,
+            top: 0,
+            left: 0,
+            bottom: 20,
+            right: 100,
+          }),
+          offsetWidth: {
+            get() {
+              return 24;
+            },
+          },
+        });
+      });
+      afterEach(() => {
+        domSpy.mockRestore();
+      });
+      it('allowDrop all nodes', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop}>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2"></TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
+      });
+      it('allowDrop no node', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop} allowDrop={() => false}>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0" className="dropTargetParent">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2"></TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
+        // not allow any dropPosition except 0 on expanded node
+        expect(onDrop).not.toHaveBeenCalled();
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+      });
+      it('not allowDrop on node which has children', () => {
+        const onDrop = jest.fn();
+        const allowDrop = ({ dropNode, dropPosition }) => {
+          if (!dropNode.children) {
+            if (dropPosition === 0) return false;
+          }
+          return true;
+        };
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop}>
+            <TreeNode key="0-0" className="dragTarget">
+              <TreeNode key="0-0-0" className="dragTargetChild" />
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2"></TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
+      });
+      it('drop to top half of first node', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 400,
+          clientY: 0,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 400,
+          clientY: -1000,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
+      });
+      it('can drop on its direct parent', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).toHaveBeenCalled();
+      });
+      it('cover window dragend & componentWillUnmount', () => {
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        window.dispatchEvent(new Event('dragend'));
+        expect(wrapper.instance().state.dragging).toEqual(false);
+        wrapper.unmount();
+      });
+      it('dragover self', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 400,
+          clientY: 500,
+        });
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 600,
+          clientY: 500,
+        });
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 600,
+          clientY: 500,
+        });
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+      });
+      it('dragover first half of non-first child', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop}>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+              <TreeNode key="0-0-1"></TreeNode>
+              <TreeNode key="0-0-2" className="dropTarget"></TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: 500,
+          clientY: 1,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: 500,
+          clientY: 1,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+      });
+    });
   });
 
   it('renders without errors', () => {
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.js
index f82da868..0bf462d9 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.js
@@ -129,7 +129,13 @@ describe('Tree Motion', () => {
       const onMotionStart = jest.fn();
       const onMotionEnd = jest.fn();
       const wrapper = mount(
-        <TreeContext.Provider value={{ prefixCls: 'test', keyEntities: {} }}>
+        <TreeContext.Provider
+          value={{
+            prefixCls: 'test',
+            keyEntities: {},
+            dropIndicatorRender: () => null,
+          }}
+        >
           <MotionTreeNode onMotionStart={onMotionStart} onMotionEnd={onMotionEnd} isEnd={[false]} />
         </TreeContext.Provider>,
       );
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index 42e7d8c1..a2eec8d7 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -13,6 +13,19 @@ exports[`Tree Basic check basic render 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -28,6 +41,10 @@ exports[`Tree Basic check basic render 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -130,6 +147,19 @@ exports[`Tree Basic check check after data ready works 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -145,6 +175,10 @@ exports[`Tree Basic check check after data ready works 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -216,6 +250,19 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -231,6 +278,10 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -271,6 +322,19 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -286,6 +350,10 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -306,6 +374,10 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -343,6 +415,19 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -358,6 +443,10 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -378,6 +467,10 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -471,6 +564,19 @@ exports[`Tree Basic renders correctly 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -486,6 +592,10 @@ exports[`Tree Basic renders correctly 1`] = `
           <div
             class="spe rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -656,6 +766,19 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -671,6 +794,10 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_close"
             />
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index f34501b0..e5f1f17e 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -13,6 +13,19 @@ exports[`TreeNode Props className 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -28,6 +41,10 @@ exports[`TreeNode Props className 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -135,6 +152,10 @@ exports[`TreeNode Props className 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -172,6 +193,19 @@ exports[`TreeNode Props customize icon component 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -187,6 +221,10 @@ exports[`TreeNode Props customize icon component 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -228,6 +266,19 @@ exports[`TreeNode Props customize icon element 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -243,6 +294,10 @@ exports[`TreeNode Props customize icon element 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -284,6 +339,19 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -299,6 +367,10 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -333,6 +405,19 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -349,6 +434,10 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             aria-label="0-0"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -460,6 +549,10 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             aria-label="0-1"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -497,6 +590,19 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -513,6 +619,10 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
             data-test="0-0"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -624,6 +734,10 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             data-test="0-1"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -661,6 +775,19 @@ exports[`TreeNode Props isLeaf 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -676,6 +803,10 @@ exports[`TreeNode Props isLeaf 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -735,6 +866,19 @@ exports[`TreeNode Props isLeaf 2`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -750,6 +894,10 @@ exports[`TreeNode Props isLeaf 2`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -815,6 +963,19 @@ exports[`TreeNode Props isLeaf 3`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -830,6 +991,10 @@ exports[`TreeNode Props isLeaf 3`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index 5d70c6ac..8e07d666 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -13,6 +13,19 @@ exports[`Tree Props checkStrictly 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -28,6 +41,10 @@ exports[`Tree Props checkStrictly 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -99,6 +116,19 @@ exports[`Tree Props checkable default 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -114,6 +144,10 @@ exports[`Tree Props checkable default 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -185,6 +219,19 @@ exports[`Tree Props checkable without selectable 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -200,6 +247,10 @@ exports[`Tree Props checkable without selectable 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -271,6 +322,19 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -286,6 +350,10 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -306,6 +374,10 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             >
@@ -403,6 +475,19 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -418,6 +503,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             >
@@ -442,6 +531,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             >
@@ -466,6 +559,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -550,6 +647,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             >
@@ -591,6 +692,19 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     aria-label="name"
     class="rc-tree-list"
@@ -623,6 +737,19 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     data-test="tree"
@@ -655,6 +782,19 @@ exports[`Tree Props defaultExpandAll 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -670,6 +810,10 @@ exports[`Tree Props defaultExpandAll 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -736,6 +880,19 @@ exports[`Tree Props disabled basic 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -751,6 +908,10 @@ exports[`Tree Props disabled basic 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -789,6 +950,19 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -804,6 +978,10 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -841,6 +1019,19 @@ exports[`Tree Props icon 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -856,6 +1047,10 @@ exports[`Tree Props icon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -929,6 +1124,19 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -944,6 +1152,10 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -1015,6 +1227,19 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -1030,6 +1255,10 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -1101,6 +1330,19 @@ exports[`Tree Props loadData basic 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -1116,6 +1358,10 @@ exports[`Tree Props loadData basic 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -1175,6 +1421,19 @@ exports[`Tree Props multiple 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -1190,6 +1449,10 @@ exports[`Tree Props multiple 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -1255,6 +1518,19 @@ exports[`Tree Props prefixCls 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -1286,6 +1562,19 @@ exports[`Tree Props prefixCls 2`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="test-prefix-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="test-prefix-indent"
+    >
+      <div
+        class="test-prefix-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="test-prefix-list"
     style="position:relative"
@@ -1317,6 +1606,19 @@ exports[`Tree Props selectable with selectable 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -1332,6 +1634,10 @@ exports[`Tree Props selectable with selectable 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -1397,6 +1703,19 @@ exports[`Tree Props selectable without selectable 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -1412,6 +1731,10 @@ exports[`Tree Props selectable without selectable 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -1477,6 +1800,19 @@ exports[`Tree Props showIcon 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -1492,6 +1828,10 @@ exports[`Tree Props showIcon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_close"
             />
@@ -1512,6 +1852,10 @@ exports[`Tree Props showIcon 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -1549,6 +1893,19 @@ exports[`Tree Props showIcon 2`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -1564,6 +1921,10 @@ exports[`Tree Props showIcon 2`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_close"
             />
@@ -1581,6 +1942,10 @@ exports[`Tree Props showIcon 2`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -1615,6 +1980,19 @@ exports[`Tree Props showIcon 3`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -1745,6 +2123,10 @@ exports[`Tree Props showIcon 3`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -1782,6 +2164,19 @@ exports[`Tree Props showLine 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position:relative"
@@ -1813,6 +2208,19 @@ exports[`Tree Props treeData 1`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -1828,6 +2236,10 @@ exports[`Tree Props treeData 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
@@ -1848,6 +2260,10 @@ exports[`Tree Props treeData 1`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher_open"
             />
@@ -2031,6 +2447,19 @@ exports[`Tree Props treeData 2`] = `
       value=""
     />
   </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
   <div
     class="rc-tree-list"
     style="position: relative;"
@@ -2046,6 +2475,10 @@ exports[`Tree Props treeData 2`] = `
           <div
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
           >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
             <span
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
diff --git a/tests/util.spec.js b/tests/util.spec.js
index adbd490c..c2e3e565 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -5,7 +5,7 @@ import Tree, { TreeNode } from '../src';
 import {
   convertDataToTree,
   conductExpandParent,
-  getDragNodesKeys,
+  getDragChildrenKeys,
   getDataAndAria,
   parseCheckedKeys,
 } from '../src/util';
@@ -415,7 +415,7 @@ describe('Util', () => {
     expect(keys.sort()).toEqual(['bamboo', 'is', 'good'].sort());
   });
 
-  it('getDragNodesKeys', () => {
+  it('getDragChildrenKeys', () => {
     const tree = (
       <Tree defaultExpandAll>
         <TreeNode key="000">
@@ -428,11 +428,11 @@ describe('Util', () => {
     );
 
     const { keyEntities } = convertDataToEntities(convertTreeToData(tree.props.children));
-    const keys0 = getDragNodesKeys('000', keyEntities);
-    expect(keys0.sort()).toEqual(['000', '111', '222', '333'].sort());
+    const keys0 = getDragChildrenKeys('000', keyEntities);
+    expect(keys0.sort()).toEqual(['111', '222', '333'].sort());
 
-    const keys1 = getDragNodesKeys('111', keyEntities);
-    expect(keys1.sort()).toEqual(['111', '222', '333'].sort());
+    const keys1 = getDragChildrenKeys('111', keyEntities);
+    expect(keys1.sort()).toEqual(['222', '333'].sort());
   });
 
   describe('getDataAndAria', () => {

From 990150ccf981937d0b39f654c28fca664d4eb88f Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Fri, 23 Oct 2020 11:13:34 +0800
Subject: [PATCH 149/397] 4.0.0-beta.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f15908e0..3eac1d15 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.10.0",
+  "version": "4.0.0-beta.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 2ac5747b8e6fc64ed8203e4c11ed208eaf4e2d65 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Sat, 24 Oct 2020 18:16:38 +0800
Subject: [PATCH 150/397] docs: dropIndicatorRender prop (#386)

---
 README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 2184bca8..5b5425ba 100644
--- a/README.md
+++ b/README.md
@@ -94,7 +94,8 @@ see examples
 | onSelect | click the treeNode to fire | function(selectedKeys, e:{selected: bool, selectedNodes, node, event, nativeEvent}) | - |
 | switcherIcon | specific the switcher icon. | ReactNode / (props: TreeNodeAttribute) => ReactNode | - |
 | virtual | Disable virtual scroll when `false` | boolean | - |
-| allowDrop | whether to allow drop on node | ({ dropNode, dropPosition }) => boolean | - |
+| allowDrop | Whether to allow drop on node | ({ dropNode, dropPosition }) => boolean | - |
+| dropIndicatorRender | The indicator to render when dragging | ({ dropPosition, dropLevelOffset, indent: number, prefixCls }) => ReactNode| - |
 
 ### TreeNode props
 

From f78bd4e3cc08353fa470454f47d0ba129c65d44e Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Sat, 24 Oct 2020 18:30:16 +0800
Subject: [PATCH 151/397] feat: add prop direction

---
 README.md    |  1 +
 src/Tree.tsx | 11 +++++++++--
 src/util.tsx |  3 ++-
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 5b5425ba..0a9c1623 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,7 @@ see examples
 | virtual | Disable virtual scroll when `false` | boolean | - |
 | allowDrop | Whether to allow drop on node | ({ dropNode, dropPosition }) => boolean | - |
 | dropIndicatorRender | The indicator to render when dragging | ({ dropPosition, dropLevelOffset, indent: number, prefixCls }) => ReactNode| - |
+| direction | Display direction of the tree, it may change drag logic | `ltr` \| `rtl` | `ltr` |
 
 ### TreeNode props
 
diff --git a/src/Tree.tsx b/src/Tree.tsx
index f7beca75..e44efde5 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -92,6 +92,7 @@ export interface TreeProps {
     dropLevelOffset: number;
     indent: number;
     prefixCls: string;
+    direction: 'ltr'
   }) => React.ReactNode;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
@@ -156,6 +157,9 @@ export interface TreeProps {
   height?: number;
   itemHeight?: number;
   virtual?: boolean;
+
+  // direction for drag logic
+  direction?: 'ltr' | 'rtl'
 }
 
 interface TreeState {
@@ -211,6 +215,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     defaultExpandedKeys: [],
     defaultCheckedKeys: [],
     defaultSelectedKeys: [],
+    direction: 'ltr',
     dropIndicatorRender: DropIndicator,
     allowDrop: () => true,
   };
@@ -429,7 +434,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       flattenNodes,
       indent,
     } = this.state;
-    const { onDragEnter, onExpand, allowDrop } = this.props;
+    const { onDragEnter, onExpand, allowDrop, direction } = this.props;
     const { pos } = node.props;
     const { dragNode } = this;
 
@@ -450,6 +455,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       flattenNodes,
       keyEntities,
       expandedKeys,
+      direction,
     );
 
     if (
@@ -551,7 +557,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       expandedKeys,
       indent,
     } = this.state;
-    const { onDragOver, allowDrop } = this.props;
+    const { onDragOver, allowDrop, direction } = this.props;
     const { dragNode } = this;
 
     const {
@@ -571,6 +577,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       flattenNodes,
       keyEntities,
       expandedKeys,
+      direction,
     );
 
     if (
diff --git a/src/util.tsx b/src/util.tsx
index 3c5c0d1c..775548ab 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -82,6 +82,7 @@ export function calcDropPosition(
   flattenedNodes: FlattenNode[],
   keyEntities: Record<Key, DataEntity>,
   expandKeys: Key[],
+  direction: 'ltr' | 'rtl',
 ) : {
   dropPosition: -1 | 0 | 1,
   dropLevelOffset: number,
@@ -94,7 +95,7 @@ export function calcDropPosition(
   const { clientX, clientY } = event;
   const { top, height } = (event.target as HTMLElement).getBoundingClientRect();
   // optional chain for testing
-  const horizontalMouseOffset = (startMousePosition?.x || 0) - clientX;
+  const horizontalMouseOffset = (direction === 'rtl' ? -1 : 1) * ((startMousePosition?.x || 0) - clientX);
   const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent;
 
   // find abstract drop node by horizontal offset

From 3abd2bff4005d441f5743316b59595a79e0eeed8 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Sat, 24 Oct 2020 23:40:35 +0800
Subject: [PATCH 152/397] feat: direction ltr or rtl

---
 src/Tree.tsx        | 7 +++++--
 src/TreeNode.tsx    | 3 ++-
 src/contextTypes.ts | 2 ++
 src/interface.tsx   | 2 ++
 src/util.tsx        | 4 ++--
 5 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index e44efde5..5af03197 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -33,6 +33,7 @@ import {
   EventDataNode,
   NodeInstance,
   ScrollTo,
+  Direction,
 } from './interface';
 import {
   flattenTreeData,
@@ -92,7 +93,7 @@ export interface TreeProps {
     dropLevelOffset: number;
     indent: number;
     prefixCls: string;
-    direction: 'ltr'
+    direction: Direction;
   }) => React.ReactNode;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
@@ -159,7 +160,7 @@ export interface TreeProps {
   virtual?: boolean;
 
   // direction for drag logic
-  direction?: 'ltr' | 'rtl'
+  direction?: Direction
 }
 
 interface TreeState {
@@ -1278,6 +1279,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       titleRender,
       dropIndicatorRender,
       onContextMenu,
+      direction,
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
 
@@ -1300,6 +1302,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
           dropPosition,
           dragOverNodeKey,
           indent,
+          direction,
           dropIndicatorRender,
 
           loadData,
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index c3b4df7e..dfc7f16d 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -471,13 +471,14 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
         indent,
         dropIndicatorRender,
         dragOverNodeKey,
+        direction,
       },
     } = this.props;
     const mergedDraggable = draggable !== false;
     // allowDrop is calculated in Tree.tsx, there is no need for calc it here
     const showIndicator = !disabled && mergedDraggable && dragOverNodeKey === eventKey;
     return showIndicator
-      ? dropIndicatorRender({ dropPosition, dropLevelOffset, indent, prefixCls })
+      ? dropIndicatorRender({ dropPosition, dropLevelOffset, indent, prefixCls, direction })
       : null;
   };
 
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index ce2e5ae2..0550cf7f 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -46,8 +46,10 @@ export interface TreeContextProps {
     dropLevelOffset: number;
     indent;
     prefixCls;
+    direction: 'ltr' | 'rtl';
   }) => React.ReactNode;
   dragOverNodeKey: Key | null;
+  direction: 'ltr' | 'rtl';
 
   loadData: (treeNode: EventDataNode) => Promise<void>;
   filterTreeNode: (treeNode: EventDataNode) => boolean;
diff --git a/src/interface.tsx b/src/interface.tsx
index c8f72049..b880b5a1 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -77,3 +77,5 @@ export type ScrollTo = (scroll: { key: Key }) => void;
 export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
 
 export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;
+
+export type Direction = 'ltr' | 'rtl'
diff --git a/src/util.tsx b/src/util.tsx
index 775548ab..e8d13476 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -6,7 +6,7 @@
 import React from 'react';
 import warning from 'rc-util/lib/warning';
 import TreeNode, { TreeNodeProps } from './TreeNode';
-import { NodeElement, Key, DataNode, Entity, DataEntity, NodeInstance, FlattenNode } from './interface';
+import { NodeElement, Key, DataNode, Entity, DataEntity, NodeInstance, FlattenNode, Direction } from './interface';
 import { TreeProps, AllowDrop } from './Tree';
 
 export function arrDel(list: Key[], value: Key) {
@@ -82,7 +82,7 @@ export function calcDropPosition(
   flattenedNodes: FlattenNode[],
   keyEntities: Record<Key, DataEntity>,
   expandKeys: Key[],
-  direction: 'ltr' | 'rtl',
+  direction: Direction,
 ) : {
   dropPosition: -1 | 0 | 1,
   dropLevelOffset: number,

From ab6bc40b76c1c2967a55d2ac8af1735f221e0962 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Sat, 24 Oct 2020 23:53:25 +0800
Subject: [PATCH 153/397] docs: direction prop

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 0a9c1623..d2fcbf1a 100644
--- a/README.md
+++ b/README.md
@@ -96,7 +96,7 @@ see examples
 | virtual | Disable virtual scroll when `false` | boolean | - |
 | allowDrop | Whether to allow drop on node | ({ dropNode, dropPosition }) => boolean | - |
 | dropIndicatorRender | The indicator to render when dragging | ({ dropPosition, dropLevelOffset, indent: number, prefixCls }) => ReactNode| - |
-| direction | Display direction of the tree, it may change drag logic | `ltr` \| `rtl` | `ltr` |
+| direction | Display direction of the tree, it may affect dragging behavior | `ltr` \| `rtl` | `ltr` |
 
 ### TreeNode props
 

From f7fa8354319fd95ac12c5434a25a447992a18390 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 26 Oct 2020 20:07:43 +0800
Subject: [PATCH 154/397] fix: Fix Indent to correct level  (#390)

* test: update snapshot

* fix lint

* fix more lint

* bump ci node version
---
 .eslintrc.js                                  |  8 +--
 .travis.yml                                   |  2 +-
 assets/index.less                             |  3 +-
 examples/animation.jsx                        | 27 ++++++++--
 examples/animation.less                       | 38 ++++++++++----
 examples/big-data.jsx                         |  2 +-
 examples/dropdown.jsx                         |  7 +--
 package.json                                  |  2 +-
 src/Indent.tsx                                |  5 +-
 src/util.tsx                                  |  8 +--
 tests/__snapshots__/Tree.spec.js.snap         | 20 ++++----
 .../__snapshots__/TreeNodeProps.spec.js.snap  | 18 +++----
 tests/__snapshots__/TreeProps.spec.js.snap    | 50 +++++++++----------
 tests/util.spec.js                            | 38 ++++++++++++++
 14 files changed, 150 insertions(+), 78 deletions(-)

diff --git a/.eslintrc.js b/.eslintrc.js
index de71e3df..b885a86d 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,9 +1,7 @@
-const base = require('@umijs/fabric/dist/eslint');
-
 module.exports = {
-  ...base,
+  extends: [require.resolve('@umijs/fabric/dist/eslint')],
+
   rules: {
-    ...base.rules,
     'react/sort-comp': 0,
     'default-case': 0,
     'eslint-comments/disable-enable-pair': 0,
@@ -12,5 +10,7 @@ module.exports = {
     'jsx-a11y/label-has-for': 0,
     'jsx-a11y/label-has-associated-control': 0,
     'no-loop-func': 0,
+    '@typescript-eslint/no-loop-func': 0,
+    'max-classes-per-file': 0,
   },
 };
diff --git a/.travis.yml b/.travis.yml
index 5919a533..a04c90ed 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ notifications:
   - hualei5280@gmail.com
 
 node_js:
-- 10
+- 14
 
 before_install:
 - |
diff --git a/assets/index.less b/assets/index.less
index b7b778d3..808901d1 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -4,7 +4,7 @@
 .@{treePrefixCls} {
   margin: 0;
   border: 1px solid transparent;
-  
+
   &-focused:not(&-active-focused) {
     border-color: cyan;
   }
@@ -210,6 +210,7 @@
     vertical-align: top;
   }
   &-indent-unit {
+    width: 16px;
     display: inline-block;
     padding-left: 18px;
   }
diff --git a/examples/animation.jsx b/examples/animation.jsx
index 0b7c3233..d46cea01 100644
--- a/examples/animation.jsx
+++ b/examples/animation.jsx
@@ -81,9 +81,27 @@ function getTreeData() {
     {
       key: '1',
       title: 'node 1',
-      children: new Array(1000)
-        .fill(null)
-        .map((_, index) => ({ title: `auto ${index}`, key: `auto-${index}` })),
+      // children: new Array(1000)
+      //   .fill(null)
+      //   .map((_, index) => ({ title: `auto ${index}`, key: `auto-${index}` })),
+      children: [
+        {
+          key: '1-0',
+          title: 'node 1-0',
+          children: [
+            { key: '1-0-0', title: 'node 1-0-0' },
+            {
+              key: '1-0-1',
+              title: 'node 1-0-1',
+              children: [
+                { key: '1-0-1-0', title: 'node 1-0-1-0' },
+                { key: '1-0-1-1', title: 'node 1-0-1-1' },
+              ],
+            },
+            { key: '1-0-2', title: 'node 1-0-2' },
+          ],
+        },
+      ],
     },
   ];
 }
@@ -105,7 +123,8 @@ const Demo = () => {
           <h3>With Virtual</h3>
           <Tree
             ref={treeRef}
-            defaultExpandAll={false}
+            // defaultExpandAll={false}
+            defaultExpandAll
             defaultExpandedKeys={defaultExpandedKeys}
             motion={motion}
             height={200}
diff --git a/examples/animation.less b/examples/animation.less
index 6cc0a798..702b7b0c 100644
--- a/examples/animation.less
+++ b/examples/animation.less
@@ -5,6 +5,9 @@
     .rc-tree-indent {
       position: relative;
       align-self: stretch;
+      display: flex;
+      flex-wrap: nowrap;
+      align-items: stretch;
 
       .rc-tree-indent-unit {
         position: relative;
@@ -12,29 +15,46 @@
 
         &::before {
           position: absolute;
-          top: 100%;
-          right: -26px;
-          bottom: -100%;
+          top: 0;
+          bottom: 0;
           border-right: 1px solid blue;
+          left: 50%;
           content: '';
         }
 
-        // End should ignore
         &-end {
           &::before {
             display: none;
           }
         }
+
+        // End should ignore
+        // &-end {
+        //   &::before {
+        //     display: none;
+        //   }
+        // }
+      }
+    }
+
+    .rc-tree-switcher-noop {
+      &::before {
+        content: '';
+        display: inline-block;
+        width: 16px;
+        height: 16px;
+        background: red;
+        border-radius: 100%;
       }
     }
 
     // Motion
     &-motion:not(.node-motion-appear-active) {
-      .rc-tree-indent-unit {
-        &::before {
-          display: none;
-        }
-      }
+      // .rc-tree-indent-unit {
+      //   &::before {
+      //     display: none;
+      //   }
+      // }
     }
   }
 }
diff --git a/examples/big-data.jsx b/examples/big-data.jsx
index 1ceba583..e5063e72 100644
--- a/examples/big-data.jsx
+++ b/examples/big-data.jsx
@@ -13,7 +13,7 @@ class Demo extends React.Component {
     selectedKeys: [],
   };
 
-  componentWillUpdate(nextProps, nextState) {
+  componentDidUpdate(nextProps, nextState) {
     // invoked immediately before rendering with new props or state, not for initial 'render'
     // see componentWillReceiveProps if you need to call setState
     // console.log(nextState.gData === this.state.gData);
diff --git a/examples/dropdown.jsx b/examples/dropdown.jsx
index ebb9bbfc..4346a34f 100644
--- a/examples/dropdown.jsx
+++ b/examples/dropdown.jsx
@@ -52,12 +52,13 @@ class DropdownTree extends React.Component {
     };
   }
 
-  componentWillReceiveProps(props) {
+  static getDerivedStateFromProps(props) {
     if ('visible' in props) {
-      this.setState({
+      return {
         visible: props.visible,
-      });
+      };
     }
+    return null;
   }
 
   onChange = value => {
diff --git a/package.json b/package.json
index c2cb2e6c..a91fd937 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
     "@types/react": "^16.8.19",
     "@types/react-dom": "^16.8.4",
     "@types/warning": "^3.0.0",
-    "@typescript-eslint/parser": "^4.0.1",
+    "@umijs/fabric": "^2.3.1",
     "css-animation": "^1.2.0",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",
diff --git a/src/Indent.tsx b/src/Indent.tsx
index 11f4cfcc..59d2cb25 100644
--- a/src/Indent.tsx
+++ b/src/Indent.tsx
@@ -20,9 +20,8 @@ const Indent: React.FC<IndentProps> = ({ prefixCls, level, isStart, isEnd }) =>
       <span
         key={i}
         className={classNames(baseClassName, {
-          [`${baseClassName}-start`]: isStart[i + 1],
-          [`${baseClassName}-end`]: isEnd[i + 1],
-          [`${baseClassName}-end-first-level`]: !i && isEnd[0],
+          [`${baseClassName}-start`]: isStart[i],
+          [`${baseClassName}-end`]: isEnd[i],
         })}
       />,
     );
diff --git a/src/util.tsx b/src/util.tsx
index 227a5ec6..e4b5fcfa 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -5,7 +5,7 @@
 import React from 'react';
 import warning from 'rc-util/lib/warning';
 import TreeNode, { TreeNodeProps } from './TreeNode';
-import { NodeElement, Key, DataNode, Entity, DataEntity, NodeInstance } from './interface';
+import { NodeElement, Key, DataNode, DataEntity, NodeInstance } from './interface';
 import { TreeProps } from './Tree';
 
 const DRAG_SIDE_RANGE = 0.25;
@@ -110,12 +110,6 @@ export function convertDataToTree(
   );
 }
 
-// TODO: ========================= NEW LOGIC =========================
-interface Wrapper {
-  posEntities: Record<string, Entity>;
-  keyEntities: Record<Key, Entity>;
-}
-
 /**
  * Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style
  */
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index 42e7d8c1..6ac2f43c 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -56,7 +56,7 @@ exports[`Tree Basic check basic render 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -87,7 +87,7 @@ exports[`Tree Basic check basic render 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -173,7 +173,7 @@ exports[`Tree Basic check check after data ready works 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -403,7 +403,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -431,7 +431,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -514,7 +514,7 @@ exports[`Tree Basic renders correctly 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -545,7 +545,7 @@ exports[`Tree Basic renders correctly 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
               <span
                 class="rc-tree-indent-unit rc-tree-indent-unit-start"
@@ -579,10 +579,10 @@ exports[`Tree Basic renders correctly 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -613,7 +613,7 @@ exports[`Tree Basic renders correctly 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index f34501b0..2c21f0a7 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -84,7 +84,7 @@ exports[`TreeNode Props className 1`] = `
                 class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -112,7 +112,7 @@ exports[`TreeNode Props className 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -407,7 +407,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
                 class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -436,7 +436,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -571,7 +571,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
                 class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -600,7 +600,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -698,7 +698,7 @@ exports[`TreeNode Props isLeaf 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -775,7 +775,7 @@ exports[`TreeNode Props isLeaf 2`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -855,7 +855,7 @@ exports[`TreeNode Props isLeaf 3`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index 5d70c6ac..b0df6d0a 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -56,7 +56,7 @@ exports[`Tree Props checkStrictly 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -142,7 +142,7 @@ exports[`Tree Props checkable default 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -228,7 +228,7 @@ exports[`Tree Props checkable without selectable 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -335,7 +335,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -363,7 +363,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -491,7 +491,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+                class="rc-tree-indent-unit"
               />
             </span>
             <span
@@ -523,7 +523,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit"
               />
             </span>
             <span
@@ -695,7 +695,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -885,7 +885,7 @@ exports[`Tree Props icon 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -972,7 +972,7 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -1058,7 +1058,7 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -1138,7 +1138,7 @@ exports[`Tree Props loadData basic 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -1215,7 +1215,7 @@ exports[`Tree Props multiple 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -1357,7 +1357,7 @@ exports[`Tree Props selectable with selectable 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -1437,7 +1437,7 @@ exports[`Tree Props selectable without selectable 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -1635,7 +1635,7 @@ exports[`Tree Props showIcon 3`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -1694,7 +1694,7 @@ exports[`Tree Props showIcon 3`] = `
                 class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -1722,7 +1722,7 @@ exports[`Tree Props showIcon 3`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit rc-tree-indent-unit-start"
               />
             </span>
             <span
@@ -1873,7 +1873,7 @@ exports[`Tree Props treeData 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -1901,7 +1901,7 @@ exports[`Tree Props treeData 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
               />
             </span>
             <span
@@ -1929,10 +1929,10 @@ exports[`Tree Props treeData 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
               />
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-start"
+                class="rc-tree-indent-unit"
               />
             </span>
             <span
@@ -1960,10 +1960,10 @@ exports[`Tree Props treeData 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
               />
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end"
+                class="rc-tree-indent-unit"
               />
             </span>
             <span
@@ -1991,7 +1991,7 @@ exports[`Tree Props treeData 1`] = `
               class="rc-tree-indent"
             >
               <span
-                class="rc-tree-indent-unit rc-tree-indent-unit-end rc-tree-indent-unit-end-first-level"
+                class="rc-tree-indent-unit rc-tree-indent-unit-end"
               />
             </span>
             <span
diff --git a/tests/util.spec.js b/tests/util.spec.js
index adbd490c..0a4073e6 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -516,6 +516,44 @@ describe('Util', () => {
         '1',
       ]);
     });
+
+    it('isEnd should be correct', () => {
+      const flattenList = flattenTreeData(
+        [
+          getNode('0', [
+            getNode('0-0'),
+            getNode('0-1'),
+            getNode('0-2', [
+              // Break lines
+              getNode('0-2-0'),
+            ]),
+          ]),
+          getNode('1', [
+            // Break lines
+            getNode('1-0'),
+            getNode('1-1'),
+          ]),
+        ],
+        true,
+      );
+
+      expect(
+        flattenList.map(({ isStart, isEnd }) => ({
+          isStart,
+          isEnd,
+        })),
+      ).toEqual([
+        { isStart: [true], isEnd: [false] },
+        { isStart: [true, true], isEnd: [false, false] },
+        { isStart: [true, false], isEnd: [false, false] },
+        { isStart: [true, false], isEnd: [false, true] },
+        { isStart: [true, false, true], isEnd: [false, true, true] },
+
+        { isStart: [false], isEnd: [true] },
+        { isStart: [false, true], isEnd: [true, false] },
+        { isStart: [false, false], isEnd: [true, true] },
+      ]);
+    });
   });
 
   it('not crash if node not exist with getTreeNodeProps', () => {

From 5343e8731c598e18661a79d93f75c51f1e9624b8 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 26 Oct 2020 20:37:37 +0800
Subject: [PATCH 155/397] chore: Add np any branch config

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a91fd937..dd26d28c 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
     "start": "father doc dev --storybook",
     "build": "father doc build --storybook",
     "compile": "father build && lessc assets/index.less assets/index.css",
-    "prepublishOnly": "npm run compile && np --yolo --no-publish",
+    "prepublishOnly": "npm run compile && np --yolo --no-publish --any-branch",
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js",
     "test": "father test",

From 1a01dce2b1ad159de86f45a55b4f8a47defaabd2 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 26 Oct 2020 20:39:19 +0800
Subject: [PATCH 156/397] 3.11.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index dd26d28c..3de22581 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "3.10.0",
+  "version": "3.11.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 6ec492241a451dd9204845952237847e2c75c6ed Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Tue, 27 Oct 2020 20:18:27 +0800
Subject: [PATCH 157/397] test: rtl test

---
 README.md           |   2 +-
 src/Tree.tsx        |   1 -
 src/contextTypes.ts |   6 +-
 src/interface.tsx   |   2 +-
 tests/Tree.spec.js  | 764 ++++++++++++++++++++++----------------------
 5 files changed, 391 insertions(+), 384 deletions(-)

diff --git a/README.md b/README.md
index d2fcbf1a..3c10147b 100644
--- a/README.md
+++ b/README.md
@@ -96,7 +96,7 @@ see examples
 | virtual | Disable virtual scroll when `false` | boolean | - |
 | allowDrop | Whether to allow drop on node | ({ dropNode, dropPosition }) => boolean | - |
 | dropIndicatorRender | The indicator to render when dragging | ({ dropPosition, dropLevelOffset, indent: number, prefixCls }) => ReactNode| - |
-| direction | Display direction of the tree, it may affect dragging behavior | `ltr` \| `rtl` | `ltr` |
+| direction | Display direction of the tree, it may affect dragging behavior | `ltr` \| `rtl` | - |
 
 ### TreeNode props
 
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 5af03197..723c46d9 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -216,7 +216,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
     defaultExpandedKeys: [],
     defaultCheckedKeys: [],
     defaultSelectedKeys: [],
-    direction: 'ltr',
     dropIndicatorRender: DropIndicator,
     allowDrop: () => true,
   };
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 0550cf7f..a7b16bf2 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -3,7 +3,7 @@
  * When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
  */
 import * as React from 'react';
-import { IconType, Key, DataEntity, EventDataNode, NodeInstance, DataNode } from './interface';
+import { IconType, Key, DataEntity, EventDataNode, NodeInstance, DataNode, Direction } from './interface';
 
 export type NodeMouseEventParams<T = HTMLSpanElement> = {
   event: React.MouseEvent<T>;
@@ -46,10 +46,10 @@ export interface TreeContextProps {
     dropLevelOffset: number;
     indent;
     prefixCls;
-    direction: 'ltr' | 'rtl';
+    direction: Direction;
   }) => React.ReactNode;
   dragOverNodeKey: Key | null;
-  direction: 'ltr' | 'rtl';
+  direction: Direction;
 
   loadData: (treeNode: EventDataNode) => Promise<void>;
   filterTreeNode: (treeNode: EventDataNode) => boolean;
diff --git a/src/interface.tsx b/src/interface.tsx
index b880b5a1..1958395e 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -78,4 +78,4 @@ export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
 
 export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;
 
-export type Direction = 'ltr' | 'rtl'
+export type Direction = 'ltr' | 'rtl' | undefined
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 1c6b31f3..192e6301 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1279,405 +1279,413 @@ describe('Tree Basic', () => {
       afterEach(() => {
         domSpy.mockRestore();
       });
-      it('allowDrop all nodes', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop}>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+      ['ltr', 'rtl'].forEach(dir => {
+        const base = dir === 'ltr' ? 1 : -1
+        it('allowDrop all nodes', () => {
+          const onDrop = jest.fn();
+          const wrapper = mount(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" className="dropTarget" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2"></TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 600,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 600,
-          clientY: 600,
+              <TreeNode key="0-2"></TreeNode>
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 500,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 500,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 550,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 550,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 600,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 600,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
         });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
-      });
-      it('allowDrop no node', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop} allowDrop={() => false}>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+        it('allowDrop no node', () => {
+          const onDrop = jest.fn();
+          const wrapper = mount(
+            <Tree draggable defaultExpandAll onDrop={onDrop} allowDrop={() => false}
+            direction={dir}>
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0" className="dropTargetParent">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0" className="dropTargetParent">
+                  <TreeNode key="0-1-0-0" className="dropTarget" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2"></TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
-        // not allow any dropPosition except 0 on expanded node
-        expect(onDrop).not.toHaveBeenCalled();
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 600,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 600,
-          clientY: 600,
+              <TreeNode key="0-2"></TreeNode>
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
+          // not allow any dropPosition except 0 on expanded node
+          expect(onDrop).not.toHaveBeenCalled();
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop).not.toHaveBeenCalled();
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 500,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 500,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop).not.toHaveBeenCalled();
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 550,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 550,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop).not.toHaveBeenCalled();
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 600,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 600,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop).not.toHaveBeenCalled();
         });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-      });
-      it('not allowDrop on node which has children', () => {
-        const onDrop = jest.fn();
-        const allowDrop = ({ dropNode, dropPosition }) => {
-          if (!dropNode.children) {
-            if (dropPosition === 0) return false;
-          }
-          return true;
-        };
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop}>
-            <TreeNode key="0-0" className="dragTarget">
-              <TreeNode key="0-0-0" className="dragTargetChild" />
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
+        it('drop to top half of first node', () => {
+          const onDrop = jest.fn();
+          const wrapper = mount(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-1" className="dropTarget">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2"></TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 600,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 600,
-          clientY: 600,
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
+              </TreeNode>
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 400,
+            clientY: 0,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 400,
+            clientY: -1000,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
         });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
-      });
-      it('drop to top half of first node', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
+        it('can drop on its direct parent', () => {
+          const onDrop = jest.fn();
+          const wrapper = mount(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-1" className="dropTarget">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 400,
-          clientY: 0,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 400,
-          clientY: -1000,
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop).toHaveBeenCalled();
         });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
-      });
-      it('can drop on its direct parent', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
+        it('cover window dragend & componentWillUnmount', () => {
+          const wrapper = mount(
+            <Tree draggable defaultExpandAll direction={dir}>
+              <TreeNode key="0-1" className="dropTarget">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 500,
-          clientY: 500,
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          window.dispatchEvent(new Event('dragend'));
+          expect(wrapper.instance().state.dragging).toEqual(false);
+          wrapper.unmount();
         });
-        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).toHaveBeenCalled();
-      });
-      it('cover window dragend & componentWillUnmount', () => {
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+        it('dragover first half of non-first child', () => {
+          const onDrop = jest.fn();
+          const wrapper = mount(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
+                <TreeNode key="0-0-1"></TreeNode>
+                <TreeNode key="0-0-2" className="dropTarget"></TreeNode>
               </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 500,
+            clientY: 1,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 500,
+            clientY: 1,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
         });
-        window.dispatchEvent(new Event('dragend'));
-        expect(wrapper.instance().state.dragging).toEqual(false);
-        wrapper.unmount();
-      });
-      it('dragover self', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
+        it('dragover self', () => {
+          const onDrop = jest.fn();
+          const wrapper = mount(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-1" className="dropTarget">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 400,
-          clientY: 500,
-        });
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 600,
-          clientY: 500,
-        });
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 600,
-          clientY: 500,
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 400,
+            clientY: 500,
+          });
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 600,
+            clientY: 500,
+          });
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 600,
+            clientY: 500,
+          });
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 600,
+            clientY: 500,
+          });
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop).not.toHaveBeenCalled();
         });
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-      });
-      it('dragover first half of non-first child', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop}>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+        it('not allowDrop on node which has children', () => {
+          const onDrop = jest.fn();
+          const allowDrop = ({ dropNode, dropPosition }) => {
+            if (!dropNode.children) {
+              if (dropPosition === 0) return false;
+            }
+            return true;
+          };
+          const wrapper = mount(
+            <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0" className="dragTarget">
+                <TreeNode key="0-0-0" className="dragTargetChild" />
               </TreeNode>
-              <TreeNode key="0-0-1"></TreeNode>
-              <TreeNode key="0-0-2" className="dropTarget"></TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: 500,
-          clientY: 1,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: 500,
-          clientY: 1,
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" className="dropTarget" />
+                </TreeNode>
+              </TreeNode>
+              <TreeNode key="0-2"></TreeNode>
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 500,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 500,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 550,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 550,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 600,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 600,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
         });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
-      });
+      })
     });
   });
 

From e32249960d1d797f87f26a2233278748537fb97d Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Tue, 27 Oct 2020 20:18:47 +0800
Subject: [PATCH 158/397] test: rtl drag

---
 src/contextTypes.ts | 10 +++++++++-
 src/interface.tsx   |  2 +-
 tests/Tree.spec.js  | 13 +++++++++----
 3 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index a7b16bf2..14ef86f0 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -3,7 +3,15 @@
  * When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
  */
 import * as React from 'react';
-import { IconType, Key, DataEntity, EventDataNode, NodeInstance, DataNode, Direction } from './interface';
+import {
+  IconType,
+  Key,
+  DataEntity,
+  EventDataNode,
+  NodeInstance,
+  DataNode,
+  Direction,
+} from './interface';
 
 export type NodeMouseEventParams<T = HTMLSpanElement> = {
   event: React.MouseEvent<T>;
diff --git a/src/interface.tsx b/src/interface.tsx
index 1958395e..a3072de8 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -78,4 +78,4 @@ export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
 
 export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;
 
-export type Direction = 'ltr' | 'rtl' | undefined
+export type Direction = 'ltr' | 'rtl' | undefined;
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 192e6301..4b801bf6 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1280,7 +1280,7 @@ describe('Tree Basic', () => {
         domSpy.mockRestore();
       });
       ['ltr', 'rtl'].forEach(dir => {
-        const base = dir === 'ltr' ? 1 : -1
+        const base = dir === 'ltr' ? 1 : -1;
         it('allowDrop all nodes', () => {
           const onDrop = jest.fn();
           const wrapper = mount(
@@ -1362,8 +1362,13 @@ describe('Tree Basic', () => {
         it('allowDrop no node', () => {
           const onDrop = jest.fn();
           const wrapper = mount(
-            <Tree draggable defaultExpandAll onDrop={onDrop} allowDrop={() => false}
-            direction={dir}>
+            <Tree
+              draggable
+              defaultExpandAll
+              onDrop={onDrop}
+              allowDrop={() => false}
+              direction={dir}
+            >
               <TreeNode key="0-0" className="dragTargetParent">
                 <TreeNode key="0-0-0" className="dragTarget">
                   <TreeNode key="0-0-0-0" className="dragTargetChild" />
@@ -1685,7 +1690,7 @@ describe('Tree Basic', () => {
           expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
           expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
         });
-      })
+      });
     });
   });
 

From d1245f0bf0b5c89786d8db296e0b85b550830b64 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Tue, 27 Oct 2020 21:12:00 +0800
Subject: [PATCH 159/397] 4.0.0-beta.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 3eac1d15..aa92aabb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.0.0-beta.0",
+  "version": "4.0.0-beta.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From bbdb955e8a8ec3a49faeb29220b6a75fdad9d985 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Tue, 27 Oct 2020 21:32:43 +0800
Subject: [PATCH 160/397] 4.0.0-beta.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index aa0bbfcf..30a44303 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.0.0-beta.1",
+  "version": "4.0.0-beta.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From a3df96c79ad06782004f1044e3cfc2aa2191ad2a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 30 Nov 2020 10:24:16 +0800
Subject: [PATCH 161/397] chore: Fix ts compile

---
 src/Tree.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 723c46d9..18ad7cdf 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -880,7 +880,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeLoad = (treeNode: EventDataNode) =>
-    new Promise(resolve => {
+    new Promise<void>(resolve => {
       // We need to get the latest state of loading/loaded keys
       this.setState(({ loadedKeys = [], loadingKeys = [] }): any => {
         const { loadData, onLoad } = this.props;

From 5d1e71a3b1e8843558f5cfde5c91b11632e62fdd Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 30 Nov 2020 10:24:49 +0800
Subject: [PATCH 162/397] 4.0.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 30a44303..884a1d9d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.0.0-beta.2",
+  "version": "4.0.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 0d9301a1ee07a026e62dab784cf1dbf3b9b69ed8 Mon Sep 17 00:00:00 2001
From: justanotheranonymoususer
 <justanotheranonymoususer@users.noreply.github.com>
Date: Mon, 28 Dec 2020 05:32:37 +0200
Subject: [PATCH 163/397] Fixed zTree link and minor other fixes to readme
 (#404)

---
 README.md | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index 3c10147b..51089729 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ http://localhost:9001/
 
 online example: http://react-component.github.io/tree/
 
-## install
+## Install
 
 [![rc-tree](https://nodei.co/npm/rc-tree.png)](https://npmjs.org/package/rc-tree)
 
@@ -117,7 +117,7 @@ see examples
 | icon | customize icon. When you pass component, whose render will receive full TreeNode props as component props | element/Function(props) | - |
 | switcherIcon | specific the switcher icon. | ReactNode / (props: TreeNodeAttribute) => ReactNode | - |
 
-## note
+## Note
 
 The number of treeNodes can be very large, but when enable `checkable`, it will spend more computing time, so we cached some calculations(e.g. `this.treeNodesStates`), to avoid double computing. But, this bring some restrictions, **when you async load treeNodes, you should render tree like this** `{this.state.treeData.length ? <Tree ...>{this.state.treeData.map(t => <TreeNode ... />)}</Tree> : 'loading tree'}`
 
@@ -140,10 +140,10 @@ http://localhost:8018/node_modules/rc-server/node_modules/node-jscover/lib/front
 
 rc-tree is released under the MIT license.
 
-## other tree view
+## Other tree views
 
-- [ztree](http://www.ztree.me/)
-- [jqTree](http://mbraak.github.io/jqTree/)
-- [jquery.treeselect](http://travistidwell.com/jquery.treeselect.js/)
-- [angular Select Tree](http://a5hik.github.io/angular-multi-select-tree/)
+- [zTree](http://www.treejs.cn/)
+- [jqTree](https://mbraak.github.io/jqTree/)
+- [jquery.treeselect](https://travistidwell.com/jquery.treeselect.js/)
+- [Angular Multi Select Tree](https://a5hik.github.io/angular-multi-select-tree/)
 

From 3e302ebb8c85b7c62406d480eb3209f2d37633c1 Mon Sep 17 00:00:00 2001
From: atzcl <atzcl0310@gmail.com>
Date: Mon, 28 Dec 2020 12:01:30 +0800
Subject: [PATCH 164/397] fix(typings): correct the definition of ScrollTo
 (#393)

---
 src/interface.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/interface.tsx b/src/interface.tsx
index a3072de8..5c903476 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -1,6 +1,8 @@
 import * as React from 'react';
 import { TreeNodeProps } from './TreeNode';
 
+export { ScrollTo } from 'rc-virtual-list/lib/List';
+
 export interface DataNode {
   checkable?: boolean;
   children?: DataNode[];
@@ -72,8 +74,6 @@ export interface FlattenNode {
   isEnd: boolean[];
 }
 
-export type ScrollTo = (scroll: { key: Key }) => void;
-
 export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
 
 export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;

From c12a166ff42d8fbf2db66d5739de7cf2daa9eed7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 28 Dec 2020 14:40:17 +0800
Subject: [PATCH 165/397] Feat traverse (#406)

* feat: convert support childrenPropName

* test: add test case

* chore: comment

* chore: Use github actions

* fix: Compatible logic
---
 .eslintrc.js                    |   3 +
 .github/workflows/main.yml      | 113 ++++++++++++++++++++++++++++++
 .travis.yml                     |  30 --------
 README.md                       |   6 +-
 examples/basic.jsx              |   3 +-
 examples/custom-switch-icon.jsx |   1 -
 package.json                    |   2 +-
 src/util.tsx                    |   2 +-
 src/utils/treeUtil.ts           |  41 +++++++++--
 tests/util.spec.js              | 119 ++++++++++++++++++++++----------
 tsconfig.json                   |  16 +++++
 11 files changed, 257 insertions(+), 79 deletions(-)
 create mode 100644 .github/workflows/main.yml
 delete mode 100644 .travis.yml
 create mode 100644 tsconfig.json

diff --git a/.eslintrc.js b/.eslintrc.js
index b885a86d..a491cd86 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -7,10 +7,13 @@ module.exports = {
     'eslint-comments/disable-enable-pair': 0,
     'react/require-default-props': 0,
     'react/no-unused-prop-types': 1,
+    'react-hooks/exhaustive-deps': 0,
     'jsx-a11y/label-has-for': 0,
     'jsx-a11y/label-has-associated-control': 0,
     'no-loop-func': 0,
     '@typescript-eslint/no-loop-func': 0,
+    '@typescript-eslint/consistent-type-imports': 0,
+    '@typescript-eslint/consistent-type-definitions': 0,
     'max-classes-per-file': 0,
   },
 };
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..e4edf95f
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,113 @@
+name: CI
+
+on:
+  push:
+    branches: [master]
+  pull_request:
+    branches: [master]
+
+jobs:
+  setup:
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@master
+
+      - uses: actions/setup-node@v1
+        with:
+          node-version: '12'
+
+      - name: cache package-lock.json
+        uses: actions/cache@v2
+        with:
+          path: package-temp-dir
+          key: lock-${{ github.sha }}
+
+      - name: create package-lock.json
+        run: npm i --package-lock-only
+
+      - name: hack for singe file
+        run: |
+          if [ ! -d "package-temp-dir" ]; then
+            mkdir package-temp-dir
+          fi
+          cp package-lock.json package-temp-dir
+      - name: cache node_modules
+        id: node_modules_cache_id
+        uses: actions/cache@v2
+        with:
+          path: node_modules
+          key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
+
+      - name: install
+        if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
+        run: npm ci
+
+  lint:
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@master
+
+      - name: restore cache from package-lock.json
+        uses: actions/cache@v2
+        with:
+          path: package-temp-dir
+          key: lock-${{ github.sha }}
+
+      - name: restore cache from node_modules
+        uses: actions/cache@v2
+        with:
+          path: node_modules
+          key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
+
+      - name: lint
+        run: npm run lint
+
+    needs: setup
+
+  compile:
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@master
+
+      - name: restore cache from package-lock.json
+        uses: actions/cache@v2
+        with:
+          path: package-temp-dir
+          key: lock-${{ github.sha }}
+
+      - name: restore cache from node_modules
+        uses: actions/cache@v2
+        with:
+          path: node_modules
+          key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
+
+      - name: compile
+        run: npm run compile
+
+    needs: setup
+
+  coverage:
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@master
+
+      - name: restore cache from package-lock.json
+        uses: actions/cache@v2
+        with:
+          path: package-temp-dir
+          key: lock-${{ github.sha }}
+
+      - name: restore cache from node_modules
+        uses: actions/cache@v2
+        with:
+          path: node_modules
+          key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
+
+      - name: coverage
+        run: npm test -- --coverage && bash <(curl -s https://codecov.io/bash)
+
+    needs: setup
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index a04c90ed..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-language: node_js
-
-sudo: false
-
-notifications:
-  email:
-  - hualei5280@gmail.com
-
-node_js:
-- 14
-
-before_install:
-- |
-    if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/'
-    then
-      echo "Only docs were updated, stopping build process."
-      exit
-    fi
-script:
-- |
-    if [ "$TEST_TYPE" = test ]; then
-      npm test -- --coverage && \
-      bash <(curl -s https://codecov.io/bash)
-    else
-      npm run $TEST_TYPE
-    fi
-env:
-  matrix:
-  - TEST_TYPE=lint
-  - TEST_TYPE=test
\ No newline at end of file
diff --git a/README.md b/README.md
index 51089729..9394274c 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 Tree component.
 
 [![NPM version][npm-image]][npm-url]
-[![build status][travis-image]][travis-url]
+[![build status][github-actions-image]][github-actions-url]
 [![Test coverage][coveralls-image]][coveralls-url]
 [![Dependencies][david-image]][david-url]
 [![DevDependencies][david-dev-image]][david-dev-url]
@@ -14,8 +14,8 @@ Tree component.
 
 [npm-image]: http://img.shields.io/npm/v/rc-tree.svg?style=flat-square
 [npm-url]: http://npmjs.org/package/rc-tree
-[travis-image]: https://img.shields.io/travis/react-component/tree.svg?style=flat-square
-[travis-url]: https://travis-ci.org/react-component/tree
+[github-actions-image]: https://github.com/react-component/tree/workflows/CI/badge.svg
+[github-actions-url]: https://github.com/react-component/tree/actions
 [coveralls-image]: https://img.shields.io/coveralls/react-component/tree.svg?style=flat-square
 [coveralls-url]: https://coveralls.io/r/react-component/tree?branch=master
 [david-url]: https://david-dm.org/react-component/tree
diff --git a/examples/basic.jsx b/examples/basic.jsx
index b6dbe859..be75b079 100644
--- a/examples/basic.jsx
+++ b/examples/basic.jsx
@@ -1,5 +1,4 @@
-/* eslint-disable jsx-a11y/no-noninteractive-element-interactions,
-no-alert, no-console, react/no-find-dom-node */
+/* eslint-disable no-alert, no-console, react/no-find-dom-node */
 import React from 'react';
 import '../assets/index.less';
 import './basic.less';
diff --git a/examples/custom-switch-icon.jsx b/examples/custom-switch-icon.jsx
index 1af9e1ac..97fc7bd4 100644
--- a/examples/custom-switch-icon.jsx
+++ b/examples/custom-switch-icon.jsx
@@ -1,6 +1,5 @@
 /* eslint no-console:0 */
 /* eslint no-alert:0 */
-/* eslint jsx-a11y/no-noninteractive-element-interactions:0 */
 import React from 'react';
 import '../assets/index.less';
 import Tree, { TreeNode } from '../src';
diff --git a/package.json b/package.json
index 884a1d9d..6eb207e5 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
     "@types/react": "^16.8.19",
     "@types/react-dom": "^16.8.4",
     "@types/warning": "^3.0.0",
-    "@umijs/fabric": "^2.3.1",
+    "@umijs/fabric": "^2.4.8",
     "css-animation": "^1.2.0",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",
diff --git a/src/util.tsx b/src/util.tsx
index f724ce6d..fed0623c 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -83,7 +83,7 @@ export function calcDropPosition(
   keyEntities: Record<Key, DataEntity>,
   expandKeys: Key[],
   direction: Direction,
-) : {
+): {
   dropPosition: -1 | 0 | 1,
   dropLevelOffset: number,
   dropTargetKey: Key,
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 6b066dec..c71fb92b 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -130,6 +130,13 @@ export function flattenTreeData(
   return flattenList;
 }
 
+type ExternalGetKey = GetKey<DataNode> | string;
+
+interface TraverseDataNodesConfig {
+  childrenPropName?: string;
+  externalGetKey?: ExternalGetKey;
+}
+
 /**
  * Traverse all the data by `treeData`.
  * Please not use it out of the `rc-tree` since we may refactor this code.
@@ -144,8 +151,25 @@ export function traverseDataNodes(
     parentPos: string | number;
     level: number;
   }) => void,
-  externalGetKey?: GetKey<DataNode> | string,
+  // To avoid too many params, let use config instead of origin param
+  config?: TraverseDataNodesConfig | ExternalGetKey,
 ) {
+  // Init config
+  let externalGetKey: ExternalGetKey = null;
+  let childrenPropName: string;
+
+  const configType = typeof config;
+
+  if (configType === 'function' || configType === 'string') {
+    // Legacy getKey param
+    externalGetKey = config as ExternalGetKey;
+  } else if (config && configType === 'object') {
+    ({ childrenPropName, externalGetKey } = config as TraverseDataNodesConfig);
+  }
+
+  childrenPropName = childrenPropName || 'children';
+
+  // Get keys
   let syntheticGetKey: (node: DataNode, pos?: string) => Key;
   if (externalGetKey) {
     if (typeof externalGetKey === 'string') {
@@ -157,12 +181,13 @@ export function traverseDataNodes(
     syntheticGetKey = (node, pos) => getKey(node.key, pos);
   }
 
+  // Process
   function processNode(
     node: DataNode,
     index?: number,
     parent?: { node: DataNode; pos: string; level: number },
   ) {
-    const children = node ? node.children : dataNodes;
+    const children = node ? node[childrenPropName] : dataNodes;
     const pos = node ? getPosition(parent.pos, index) : '0';
 
     // Process node if is not root
@@ -209,13 +234,21 @@ export function convertDataToEntities(
     initWrapper,
     processEntity,
     onProcessFinished,
+    externalGetKey,
+    childrenPropName,
   }: {
     initWrapper?: (wrapper: Wrapper) => Wrapper;
     processEntity?: (entity: DataEntity, wrapper: Wrapper) => void;
     onProcessFinished?: (wrapper: Wrapper) => void;
+    externalGetKey?: ExternalGetKey;
+    childrenPropName?: string;
   } = {},
-  externalGetKey?: GetKey<DataNode> | string,
+  /** @deprecated Use `config.externalGetKey` instead */
+  legacyExternalGetKey?: ExternalGetKey,
 ) {
+  // Init config
+  const mergedExternalGetKey = externalGetKey || legacyExternalGetKey;
+
   const posEntities = {};
   const keyEntities = {};
   let wrapper = {
@@ -249,7 +282,7 @@ export function convertDataToEntities(
         processEntity(entity, wrapper);
       }
     },
-    externalGetKey,
+    { externalGetKey: mergedExternalGetKey, childrenPropName },
   );
 
   if (onProcessFinished) {
diff --git a/tests/util.spec.js b/tests/util.spec.js
index 61871fe0..ca062e4f 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -14,6 +14,7 @@ import {
   convertTreeToData,
   convertDataToEntities,
   getTreeNodeProps,
+  traverseDataNodes,
 } from '../src/utils/treeUtil';
 import { spyConsole, spyError } from './util';
 import { conductCheck } from '../src/utils/conductUtil';
@@ -112,47 +113,76 @@ describe('Util', () => {
     expect(treeNodes[0].props.children[0].props.value).toBe('0-0-0');
   });
 
-  it('convertDataToEntities', () => {
-    const entities = convertDataToEntities([{ key: 'parent', children: [{ key: 0 }, { key: 1 }] }]);
-    expect(Object.keys(entities.keyEntities).sort()).toEqual(['0', '1', 'parent']);
-  });
+  describe('convertDataToEntities', () => {
+    it('basic', () => {
+      const entities = convertDataToEntities([
+        { key: 'parent', children: [{ key: 0 }, { key: 1 }] },
+      ]);
+      expect(Object.keys(entities.keyEntities).sort()).toEqual(['0', '1', 'parent']);
+    });
 
-  it('convertDataToEntities with string externalGetKey', () => {
-    const entities = convertDataToEntities(
-      [
-        {
-          key: 'parent',
-          notKey: 'let it be',
-          children: [{ key: 0, notKey: 'penny lane' }, { key: 1, notKey: 'please please me' }],
-        },
-      ],
-      undefined,
-      'notKey',
-    );
-    expect(Object.keys(entities.keyEntities).sort()).toEqual([
-      'let it be',
-      'penny lane',
-      'please please me',
-    ]);
-  });
+    it('with string externalGetKey', () => {
+      const entities = convertDataToEntities(
+        [
+          {
+            key: 'parent',
+            notKey: 'let it be',
+            children: [
+              { key: 0, notKey: 'penny lane' },
+              { key: 1, notKey: 'please please me' },
+            ],
+          },
+        ],
+        undefined,
+        'notKey',
+      );
+      expect(Object.keys(entities.keyEntities).sort()).toEqual([
+        'let it be',
+        'penny lane',
+        'please please me',
+      ]);
+    });
+
+    it('with function externalGetKey', () => {
+      const entities = convertDataToEntities(
+        [
+          {
+            key: 'parent',
+            notKey: 'let it be',
+            children: [
+              { key: 0, notKey: 'penny lane' },
+              { key: 1, notKey: 'please please me' },
+            ],
+          },
+        ],
+        undefined,
+        entity => entity.notKey,
+      );
+      expect(Object.keys(entities.keyEntities).sort()).toEqual([
+        'let it be',
+        'penny lane',
+        'please please me',
+      ]);
+    });
 
-  it('convertDataToEntities with function externalGetKey', () => {
-    const entities = convertDataToEntities(
-      [
+    it('with childrenPropName', () => {
+      const entities = convertDataToEntities(
+        [
+          {
+            rawKey: 'light',
+            childList: [{ rawKey: 'bamboo' }],
+          },
+        ],
         {
-          key: 'parent',
-          notKey: 'let it be',
-          children: [{ key: 0, notKey: 'penny lane' }, { key: 1, notKey: 'please please me' }],
+          externalGetKey: entity => entity.rawKey,
+          childrenPropName: 'childList',
         },
-      ],
-      undefined,
-      entity => entity.notKey,
-    );
-    expect(Object.keys(entities.keyEntities).sort()).toEqual([
-      'let it be',
-      'penny lane',
-      'please please me',
-    ]);
+      );
+      expect(Object.keys(entities.keyEntities).sort()).toEqual(['bamboo', 'light']);
+
+      expect(entities.keyEntities.light.children).toEqual([entities.keyEntities.bamboo]);
+      expect(entities.keyEntities.bamboo.parent).toBe(entities.keyEntities.light);
+    });
   });
 
   it('convertTreeToEntities with additional handler', () => {
@@ -582,4 +612,19 @@ describe('Util', () => {
       }),
     );
   });
+
+  it('traverseDataNodes legacy externalGetKey', () => {
+    let count = 0;
+
+    traverseDataNodes(
+      [{ rawKey: 'light' }],
+      data => {
+        count += 1;
+        expect(data.key).toBe('light');
+      },
+      'rawKey',
+    );
+
+    expect(count).toEqual(1);
+  });
 });
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..35ec7c4a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "target": "esnext",
+    "moduleResolution": "node",
+    "baseUrl": "./",
+    "jsx": "preserve",
+    "declaration": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "paths": {
+      "@/*": ["src/*"],
+      "@@/*": ["src/.umi/*"],
+      "rc-table": ["src/index.ts"]
+    }
+  }
+}

From fda137afc37a64282902c72741f7e7075ae91610 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 28 Dec 2020 14:43:47 +0800
Subject: [PATCH 166/397] 4.1.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6eb207e5..37de284a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.0.0",
+  "version": "4.1.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 505714e614bf4658d31a6879e05e68a9e0cf3139 Mon Sep 17 00:00:00 2001
From: liuchao233 <36943472+liuchao233@users.noreply.github.com>
Date: Thu, 31 Dec 2020 18:00:04 +0800
Subject: [PATCH 167/397] fix issue #28349 (#401)

---
 src/TreeNode.tsx   |  2 ++
 tests/Tree.spec.js | 43 ++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 40 insertions(+), 5 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index dfc7f16d..f2f0f3f8 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -217,8 +217,10 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
   // Disabled item still can be switch
   onExpand: React.MouseEventHandler<HTMLDivElement> = e => {
     const {
+      loading,
       context: { onNodeExpand },
     } = this.props;
+    if (loading) return;
     onNodeExpand(e, convertNodePropsToEventData(this.props));
   };
 
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 4b801bf6..54c7bc89 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1295,7 +1295,7 @@ describe('Tree Basic', () => {
                   <TreeNode key="0-1-0-0" className="dropTarget" />
                 </TreeNode>
               </TreeNode>
-              <TreeNode key="0-2"></TreeNode>
+              <TreeNode key="0-2" />
             </Tree>,
           );
           wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
@@ -1379,7 +1379,7 @@ describe('Tree Basic', () => {
                   <TreeNode key="0-1-0-0" className="dropTarget" />
                 </TreeNode>
               </TreeNode>
-              <TreeNode key="0-2"></TreeNode>
+              <TreeNode key="0-2" />
             </Tree>,
           );
           wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
@@ -1548,8 +1548,8 @@ describe('Tree Basic', () => {
                 <TreeNode key="0-0-0" className="dragTarget">
                   <TreeNode key="0-0-0-0" className="dragTargetChild" />
                 </TreeNode>
-                <TreeNode key="0-0-1"></TreeNode>
-                <TreeNode key="0-0-2" className="dropTarget"></TreeNode>
+                <TreeNode key="0-0-1" />
+                <TreeNode key="0-0-2" className="dropTarget" />
               </TreeNode>
             </Tree>,
           );
@@ -1626,7 +1626,7 @@ describe('Tree Basic', () => {
                   <TreeNode key="0-1-0-0" className="dropTarget" />
                 </TreeNode>
               </TreeNode>
-              <TreeNode key="0-2"></TreeNode>
+              <TreeNode key="0-2" />
             </Tree>,
           );
           wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
@@ -1889,4 +1889,37 @@ describe('Tree Basic', () => {
 
     expect(wrapper.find('List').props().virtual).toBe(false);
   });
+
+  // https://github.com/ant-design/ant-design/issues/28349
+  it('should not trigger expend when loading data', () => {
+    const then = jest.fn(() => Promise.resolve());
+    const loadData = jest.fn(() => ({ then }));
+    const onExpand = jest.fn();
+    const wrapper = mount(
+      <Tree loadData={loadData} onExpand={onExpand}>
+        <TreeNode title="parent 1" key="0-0" />
+      </Tree>,
+    );
+
+    // trigger click to expand node
+    wrapper.find('.rc-tree-switcher').simulate('click');
+    expect(
+      wrapper
+        .find('.rc-tree-switcher')
+        .first()
+        .is(OPEN_CLASSNAME),
+    ).toBe(true);
+    expect(onExpand).toBeCalled();
+
+    // click again
+    onExpand.mockReset();
+    wrapper.find('.rc-tree-switcher').simulate('click');
+    expect(
+      wrapper
+        .find('.rc-tree-switcher')
+        .first()
+        .is(OPEN_CLASSNAME),
+    ).toBe(true);
+    expect(onExpand).not.toHaveBeenCalled();
+  });
 });

From 90b287f4b3d36b53806a48b3788799743e046cdf Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 31 Dec 2020 18:01:41 +0800
Subject: [PATCH 168/397] 4.1.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 37de284a..08c0c86a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.1.0",
+  "version": "4.1.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 39a01f0e8f7b04564ecf2c66f17d006dd7f7b3a7 Mon Sep 17 00:00:00 2001
From: xrkffgg <xrkffgg@gmail.com>
Date: Tue, 2 Feb 2021 14:57:05 +0800
Subject: [PATCH 169/397] chore: use dumi (#414)

* chore: use dumi

* fix lint

* up now
---
 .gitignore                                    |  7 ++++-
 .prettierignore                               |  7 +++++
 .umirc.ts                                     | 19 ++++++++++++
 HISTORY.md => CHANGELOG.md                    |  5 ++--
 README.md                                     | 12 ++------
 docs/changelog.md                             |  1 +
 docs/demo/animation-draggable.md              |  3 ++
 docs/demo/animation.md                        |  3 ++
 docs/demo/basic-controlled.md                 |  3 ++
 docs/demo/basic.md                            |  3 ++
 docs/demo/big-data.md                         |  3 ++
 docs/demo/contextmenu.md                      |  3 ++
 docs/demo/custom-switch-icon.md               |  3 ++
 docs/demo/draggable-allow-drop.md             |  3 ++
 docs/demo/draggable.md                        |  3 ++
 docs/demo/dropdown.md                         |  3 ++
 docs/demo/dynamic.md                          |  3 ++
 docs/demo/funtionTitle.md                     |  3 ++
 docs/demo/icon.md                             |  3 ++
 .../examples}/animation-draggable.jsx         |  4 +--
 {examples => docs/examples}/animation.jsx     |  4 +--
 {examples => docs/examples}/animation.less    |  0
 .../examples}/basic-controlled.jsx            |  4 +--
 {examples => docs/examples}/basic.jsx         |  4 +--
 {examples => docs/examples}/basic.less        |  0
 .../examples}/big-data-generator.js           |  0
 {examples => docs/examples}/big-data.jsx      |  4 +--
 {examples => docs/examples}/contextmenu.jsx   |  4 +--
 {examples => docs/examples}/contextmenu.less  |  0
 .../examples}/custom-switch-icon.jsx          |  4 +--
 .../examples}/draggable-allow-drop.jsx        |  4 +--
 {examples => docs/examples}/draggable.jsx     |  4 +--
 {examples => docs/examples}/draggable.less    |  0
 {examples => docs/examples}/dropdown.jsx      |  4 +--
 {examples => docs/examples}/dropdown.less     |  0
 {examples => docs/examples}/dynamic.jsx       |  4 +--
 {examples => docs/examples}/funtionTitle.jsx  |  4 +--
 {examples => docs/examples}/icon.jsx          |  4 +--
 {examples => docs/examples}/icon.less         |  0
 {examples => docs/examples}/longData.json     |  0
 {examples => docs/examples}/utils/dataUtil.js |  0
 docs/index.md                                 |  5 ++++
 now.json                                      |  3 ++
 package.json                                  | 11 ++++---
 tsconfig.json                                 |  2 +-
 typings.d.ts                                  |  2 ++
 update-example.js                             | 29 +++++++++++++++++++
 47 files changed, 150 insertions(+), 44 deletions(-)
 create mode 100644 .prettierignore
 create mode 100644 .umirc.ts
 rename HISTORY.md => CHANGELOG.md (96%)
 create mode 100644 docs/changelog.md
 create mode 100644 docs/demo/animation-draggable.md
 create mode 100644 docs/demo/animation.md
 create mode 100644 docs/demo/basic-controlled.md
 create mode 100644 docs/demo/basic.md
 create mode 100644 docs/demo/big-data.md
 create mode 100644 docs/demo/contextmenu.md
 create mode 100644 docs/demo/custom-switch-icon.md
 create mode 100644 docs/demo/draggable-allow-drop.md
 create mode 100644 docs/demo/draggable.md
 create mode 100644 docs/demo/dropdown.md
 create mode 100644 docs/demo/dynamic.md
 create mode 100644 docs/demo/funtionTitle.md
 create mode 100644 docs/demo/icon.md
 rename {examples => docs/examples}/animation-draggable.jsx (98%)
 rename {examples => docs/examples}/animation.jsx (98%)
 rename {examples => docs/examples}/animation.less (100%)
 rename {examples => docs/examples}/basic-controlled.jsx (98%)
 rename {examples => docs/examples}/basic.jsx (98%)
 rename {examples => docs/examples}/basic.less (100%)
 rename {examples => docs/examples}/big-data-generator.js (100%)
 rename {examples => docs/examples}/big-data.jsx (97%)
 rename {examples => docs/examples}/contextmenu.jsx (97%)
 rename {examples => docs/examples}/contextmenu.less (100%)
 rename {examples => docs/examples}/custom-switch-icon.jsx (97%)
 rename {examples => docs/examples}/draggable-allow-drop.jsx (97%)
 rename {examples => docs/examples}/draggable.jsx (97%)
 rename {examples => docs/examples}/draggable.less (100%)
 rename {examples => docs/examples}/dropdown.jsx (98%)
 rename {examples => docs/examples}/dropdown.less (100%)
 rename {examples => docs/examples}/dynamic.jsx (97%)
 rename {examples => docs/examples}/funtionTitle.jsx (97%)
 rename {examples => docs/examples}/icon.jsx (92%)
 rename {examples => docs/examples}/icon.less (100%)
 rename {examples => docs/examples}/longData.json (100%)
 rename {examples => docs/examples}/utils/dataUtil.js (100%)
 create mode 100644 docs/index.md
 create mode 100644 typings.d.ts
 create mode 100644 update-example.js

diff --git a/.gitignore b/.gitignore
index 0e633495..99bee95b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,4 +31,9 @@ lib
 /coverage
 yarn.lock
 es/
-!tests/__mocks__/rc-util/lib
\ No newline at end of file
+!tests/__mocks__/rc-util/lib
+# umi
+.umi
+.umi-production
+.umi-test
+.env.local
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..ecb24d33
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,7 @@
+**/*.svg
+**/*.ejs
+**/*.html
+package.json
+.umi
+.umi-production
+.umi-test
diff --git a/.umirc.ts b/.umirc.ts
new file mode 100644
index 00000000..2ec33e43
--- /dev/null
+++ b/.umirc.ts
@@ -0,0 +1,19 @@
+// more config: https://d.umijs.org/config
+import { defineConfig } from 'dumi';
+
+export default defineConfig({
+  title: 'rc-tree',
+  favicon:
+    'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
+  logo:
+    'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
+  outputPath: '.doc',
+  exportStatic: {},
+  styles: [
+    `
+      .markdown table {
+        width: auto !important;
+      }
+    `,
+  ]
+});
diff --git a/HISTORY.md b/CHANGELOG.md
similarity index 96%
rename from HISTORY.md
rename to CHANGELOG.md
index 4004aede..1b34e890 100644
--- a/HISTORY.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
-# History
----
+# Changelog
+
+- https://github.com/react-component/tree/releases
 
 ## 3.2.0 `2020-05-08`
 
diff --git a/README.md b/README.md
index 9394274c..76b20514 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,8 @@
 # rc-tree
 
----
-
 Tree component.
 
-[![NPM version][npm-image]][npm-url]
-[![build status][github-actions-image]][github-actions-url]
-[![Test coverage][coveralls-image]][coveralls-url]
-[![Dependencies][david-image]][david-url]
-[![DevDependencies][david-dev-image]][david-dev-url]
-[![npm download][download-image]][download-url]
-[![bundle size][bundlephobia-image]][bundlephobia-url]
+[![NPM version][npm-image]][npm-url] [![dumi](https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square)](https://github.com/umijs/dumi) [![build status][github-actions-image]][github-actions-url] [![Test coverage][coveralls-image]][coveralls-url] [![Dependencies][david-image]][david-url] [![DevDependencies][david-dev-image]][david-dev-url] [![npm download][download-image]][download-url] [![bundle size][bundlephobia-image]][bundlephobia-url]
 
 [npm-image]: http://img.shields.io/npm/v/rc-tree.svg?style=flat-square
 [npm-url]: http://npmjs.org/package/rc-tree
@@ -39,7 +31,7 @@ Tree component.
 
 http://localhost:9001/
 
-online example: http://react-component.github.io/tree/
+online example: https://tree.react-component.now.sh/
 
 ## Install
 
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 00000000..beb2eddc
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1 @@
+<embed src="../CHANGELOG.md"></embed>
diff --git a/docs/demo/animation-draggable.md b/docs/demo/animation-draggable.md
new file mode 100644
index 00000000..4f7bf4f3
--- /dev/null
+++ b/docs/demo/animation-draggable.md
@@ -0,0 +1,3 @@
+## animation-draggable
+
+<code src="../examples/animation-draggable.jsx">
diff --git a/docs/demo/animation.md b/docs/demo/animation.md
new file mode 100644
index 00000000..ff20eb94
--- /dev/null
+++ b/docs/demo/animation.md
@@ -0,0 +1,3 @@
+## animation
+
+<code src="../examples/animation.jsx">
diff --git a/docs/demo/basic-controlled.md b/docs/demo/basic-controlled.md
new file mode 100644
index 00000000..17cf6c15
--- /dev/null
+++ b/docs/demo/basic-controlled.md
@@ -0,0 +1,3 @@
+## basic-controlled
+
+<code src="../examples/basic-controlled.jsx">
diff --git a/docs/demo/basic.md b/docs/demo/basic.md
new file mode 100644
index 00000000..80be196e
--- /dev/null
+++ b/docs/demo/basic.md
@@ -0,0 +1,3 @@
+## basic
+
+<code src="../examples/basic.jsx">
diff --git a/docs/demo/big-data.md b/docs/demo/big-data.md
new file mode 100644
index 00000000..ac3490a6
--- /dev/null
+++ b/docs/demo/big-data.md
@@ -0,0 +1,3 @@
+## big-data
+
+<code src="../examples/big-data.jsx">
diff --git a/docs/demo/contextmenu.md b/docs/demo/contextmenu.md
new file mode 100644
index 00000000..551601d8
--- /dev/null
+++ b/docs/demo/contextmenu.md
@@ -0,0 +1,3 @@
+## contextmenu
+
+<code src="../examples/contextmenu.jsx">
diff --git a/docs/demo/custom-switch-icon.md b/docs/demo/custom-switch-icon.md
new file mode 100644
index 00000000..b0e63a13
--- /dev/null
+++ b/docs/demo/custom-switch-icon.md
@@ -0,0 +1,3 @@
+## custom-switch-icon
+
+<code src="../examples/custom-switch-icon.jsx">
diff --git a/docs/demo/draggable-allow-drop.md b/docs/demo/draggable-allow-drop.md
new file mode 100644
index 00000000..29611ce6
--- /dev/null
+++ b/docs/demo/draggable-allow-drop.md
@@ -0,0 +1,3 @@
+## draggable-allow-drop
+
+<code src="../examples/draggable-allow-drop.jsx">
diff --git a/docs/demo/draggable.md b/docs/demo/draggable.md
new file mode 100644
index 00000000..ab708b6a
--- /dev/null
+++ b/docs/demo/draggable.md
@@ -0,0 +1,3 @@
+## draggable
+
+<code src="../examples/draggable.jsx">
diff --git a/docs/demo/dropdown.md b/docs/demo/dropdown.md
new file mode 100644
index 00000000..8339a336
--- /dev/null
+++ b/docs/demo/dropdown.md
@@ -0,0 +1,3 @@
+## dropdown
+
+<code src="../examples/dropdown.jsx">
diff --git a/docs/demo/dynamic.md b/docs/demo/dynamic.md
new file mode 100644
index 00000000..5014c2a5
--- /dev/null
+++ b/docs/demo/dynamic.md
@@ -0,0 +1,3 @@
+## dynamic
+
+<code src="../examples/dynamic.jsx">
diff --git a/docs/demo/funtionTitle.md b/docs/demo/funtionTitle.md
new file mode 100644
index 00000000..70f6f93b
--- /dev/null
+++ b/docs/demo/funtionTitle.md
@@ -0,0 +1,3 @@
+## funtionTitle
+
+<code src="../examples/funtionTitle.jsx">
diff --git a/docs/demo/icon.md b/docs/demo/icon.md
new file mode 100644
index 00000000..aeef622b
--- /dev/null
+++ b/docs/demo/icon.md
@@ -0,0 +1,3 @@
+## icon
+
+<code src="../examples/icon.jsx">
diff --git a/examples/animation-draggable.jsx b/docs/examples/animation-draggable.jsx
similarity index 98%
rename from examples/animation-draggable.jsx
rename to docs/examples/animation-draggable.jsx
index ae5dcc75..0448d7e4 100644
--- a/examples/animation-draggable.jsx
+++ b/docs/examples/animation-draggable.jsx
@@ -2,8 +2,8 @@
 react/no-danger, no-param-reassign */
 import React from 'react';
 import { gData } from './utils/dataUtil';
-import '../assets/index.less';
-import Tree from '../src';
+import '../../assets/index.less';
+import Tree from 'rc-tree';
 
 const STYLE = `
 .rc-tree-child-tree {
diff --git a/examples/animation.jsx b/docs/examples/animation.jsx
similarity index 98%
rename from examples/animation.jsx
rename to docs/examples/animation.jsx
index d46cea01..703d491b 100644
--- a/examples/animation.jsx
+++ b/docs/examples/animation.jsx
@@ -1,8 +1,8 @@
 /* eslint no-console:0, react/no-danger: 0 */
-import '../assets/index.less';
+import '../../assets/index.less';
 import './animation.less';
 import React from 'react';
-import Tree from '../src';
+import Tree from 'rc-tree';
 
 const STYLE = `
 .rc-tree-child-tree {
diff --git a/examples/animation.less b/docs/examples/animation.less
similarity index 100%
rename from examples/animation.less
rename to docs/examples/animation.less
diff --git a/examples/basic-controlled.jsx b/docs/examples/basic-controlled.jsx
similarity index 98%
rename from examples/basic-controlled.jsx
rename to docs/examples/basic-controlled.jsx
index 18c3a64e..cbbbce00 100644
--- a/examples/basic-controlled.jsx
+++ b/docs/examples/basic-controlled.jsx
@@ -1,9 +1,9 @@
 /* eslint-disable no-console, react/no-unescaped-entities */
-import '../assets/index.less';
+import '../../assets/index.less';
 import React from 'react';
 import 'rc-dialog/assets/index.css';
 import Modal from 'rc-dialog';
-import Tree, { TreeNode } from '../src';
+import Tree, { TreeNode } from 'rc-tree';
 import { gData, getRadioSelectKeys } from './utils/dataUtil';
 
 class Demo extends React.Component {
diff --git a/examples/basic.jsx b/docs/examples/basic.jsx
similarity index 98%
rename from examples/basic.jsx
rename to docs/examples/basic.jsx
index be75b079..ab986e32 100644
--- a/examples/basic.jsx
+++ b/docs/examples/basic.jsx
@@ -1,8 +1,8 @@
 /* eslint-disable no-alert, no-console, react/no-find-dom-node */
 import React from 'react';
-import '../assets/index.less';
+import '../../assets/index.less';
 import './basic.less';
-import Tree, { TreeNode } from '../src';
+import Tree, { TreeNode } from 'rc-tree';
 
 const treeData = [
   {
diff --git a/examples/basic.less b/docs/examples/basic.less
similarity index 100%
rename from examples/basic.less
rename to docs/examples/basic.less
diff --git a/examples/big-data-generator.js b/docs/examples/big-data-generator.js
similarity index 100%
rename from examples/big-data-generator.js
rename to docs/examples/big-data-generator.js
diff --git a/examples/big-data.jsx b/docs/examples/big-data.jsx
similarity index 97%
rename from examples/big-data.jsx
rename to docs/examples/big-data.jsx
index e5063e72..657358ac 100644
--- a/examples/big-data.jsx
+++ b/docs/examples/big-data.jsx
@@ -1,8 +1,8 @@
 /* eslint-disable no-console, prefer-destructuring */
 import React from 'react';
 import Gen from './big-data-generator';
-import '../assets/index.less';
-import Tree, { TreeNode } from '../src';
+import '../../assets/index.less';
+import Tree, { TreeNode } from 'rc-tree';
 
 class Demo extends React.Component {
   state = {
diff --git a/examples/contextmenu.jsx b/docs/examples/contextmenu.jsx
similarity index 97%
rename from examples/contextmenu.jsx
rename to docs/examples/contextmenu.jsx
index 6932435a..5f8c46f5 100644
--- a/examples/contextmenu.jsx
+++ b/docs/examples/contextmenu.jsx
@@ -3,8 +3,8 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import Tooltip from 'rc-tooltip';
 import './contextmenu.less';
-import '../assets/index.less';
-import Tree, { TreeNode } from '../src';
+import '../../assets/index.less';
+import Tree, { TreeNode } from 'rc-tree';
 
 function contains(root, n) {
   let node = n;
diff --git a/examples/contextmenu.less b/docs/examples/contextmenu.less
similarity index 100%
rename from examples/contextmenu.less
rename to docs/examples/contextmenu.less
diff --git a/examples/custom-switch-icon.jsx b/docs/examples/custom-switch-icon.jsx
similarity index 97%
rename from examples/custom-switch-icon.jsx
rename to docs/examples/custom-switch-icon.jsx
index 97fc7bd4..5dfba3a3 100644
--- a/examples/custom-switch-icon.jsx
+++ b/docs/examples/custom-switch-icon.jsx
@@ -1,8 +1,8 @@
 /* eslint no-console:0 */
 /* eslint no-alert:0 */
 import React from 'react';
-import '../assets/index.less';
-import Tree, { TreeNode } from '../src';
+import '../../assets/index.less';
+import Tree, { TreeNode } from 'rc-tree';
 
 const arrowPath =
   'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h-88' +
diff --git a/examples/draggable-allow-drop.jsx b/docs/examples/draggable-allow-drop.jsx
similarity index 97%
rename from examples/draggable-allow-drop.jsx
rename to docs/examples/draggable-allow-drop.jsx
index 8570e395..fdacfa35 100644
--- a/examples/draggable-allow-drop.jsx
+++ b/docs/examples/draggable-allow-drop.jsx
@@ -2,8 +2,8 @@
 import React from 'react';
 import { gData } from './utils/dataUtil';
 import './draggable.less';
-import '../assets/index.less';
-import Tree from '../src';
+import '../../assets/index.less';
+import Tree from 'rc-tree';
 
 function allowDrop({ dropNode, dropPosition }) {
   if (!dropNode.children) {
diff --git a/examples/draggable.jsx b/docs/examples/draggable.jsx
similarity index 97%
rename from examples/draggable.jsx
rename to docs/examples/draggable.jsx
index 7ff85c2e..af611061 100644
--- a/examples/draggable.jsx
+++ b/docs/examples/draggable.jsx
@@ -2,8 +2,8 @@
 import React from 'react';
 import { gData } from './utils/dataUtil';
 import './draggable.less';
-import '../assets/index.less';
-import Tree from '../src';
+import '../../assets/index.less';
+import Tree from 'rc-tree';
 
 class Demo extends React.Component {
   state = {
diff --git a/examples/draggable.less b/docs/examples/draggable.less
similarity index 100%
rename from examples/draggable.less
rename to docs/examples/draggable.less
diff --git a/examples/dropdown.jsx b/docs/examples/dropdown.jsx
similarity index 98%
rename from examples/dropdown.jsx
rename to docs/examples/dropdown.jsx
index 4346a34f..ac5add32 100644
--- a/examples/dropdown.jsx
+++ b/docs/examples/dropdown.jsx
@@ -5,8 +5,8 @@ import React from 'react';
 import Trigger from 'rc-trigger';
 import { gData } from './utils/dataUtil';
 import './dropdown.less';
-import '../assets/index.less';
-import Tree, { TreeNode } from '../src';
+import '../../assets/index.less';
+import Tree, { TreeNode } from 'rc-tree';
 
 const placements = {
   topLeft: {
diff --git a/examples/dropdown.less b/docs/examples/dropdown.less
similarity index 100%
rename from examples/dropdown.less
rename to docs/examples/dropdown.less
diff --git a/examples/dynamic.jsx b/docs/examples/dynamic.jsx
similarity index 97%
rename from examples/dynamic.jsx
rename to docs/examples/dynamic.jsx
index 26957409..90fa1606 100644
--- a/examples/dynamic.jsx
+++ b/docs/examples/dynamic.jsx
@@ -1,7 +1,7 @@
 /* eslint-disable no-console, react/no-access-state-in-setstate */
-import '../assets/index.less';
+import '../../assets/index.less';
 import React from 'react';
-import Tree from '../src';
+import Tree from 'rc-tree';
 
 function generateTreeNodes(treeNode) {
   const arr = [];
diff --git a/examples/funtionTitle.jsx b/docs/examples/funtionTitle.jsx
similarity index 97%
rename from examples/funtionTitle.jsx
rename to docs/examples/funtionTitle.jsx
index 5a150941..338e5846 100644
--- a/examples/funtionTitle.jsx
+++ b/docs/examples/funtionTitle.jsx
@@ -1,8 +1,8 @@
 /* eslint no-console:0, react/no-danger: 0 */
-import '../assets/index.less';
+import '../../assets/index.less';
 import './animation.less';
 import React, { useState } from 'react';
-import Tree from '../src';
+import Tree from 'rc-tree';
 import data from './longData.json';
 
 const STYLE = `
diff --git a/examples/icon.jsx b/docs/examples/icon.jsx
similarity index 92%
rename from examples/icon.jsx
rename to docs/examples/icon.jsx
index 55cda45b..200f4361 100644
--- a/examples/icon.jsx
+++ b/docs/examples/icon.jsx
@@ -2,8 +2,8 @@
 /* eslint no-alert:0 */
 import React from 'react';
 import classNames from 'classnames';
-import Tree, { TreeNode } from '../src';
-import '../assets/index.less';
+import Tree, { TreeNode } from 'rc-tree';
+import '../../assets/index.less';
 import './icon.less';
 
 const Icon = ({ selected }) => (
diff --git a/examples/icon.less b/docs/examples/icon.less
similarity index 100%
rename from examples/icon.less
rename to docs/examples/icon.less
diff --git a/examples/longData.json b/docs/examples/longData.json
similarity index 100%
rename from examples/longData.json
rename to docs/examples/longData.json
diff --git a/examples/utils/dataUtil.js b/docs/examples/utils/dataUtil.js
similarity index 100%
rename from examples/utils/dataUtil.js
rename to docs/examples/utils/dataUtil.js
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..05ad80f2
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,5 @@
+---
+title: rc-tree
+---
+
+<embed src="../README.md"></embed>
diff --git a/now.json b/now.json
index 797f30a6..40647a1f 100644
--- a/now.json
+++ b/now.json
@@ -7,5 +7,8 @@
       "use": "@now/static-build",
       "config": { "distDir": ".doc" }
     }
+  ],
+  "routes": [
+    { "src": "/(.*)", "dest": "/dist/$1" }
   ]
 }
\ No newline at end of file
diff --git a/package.json b/package.json
index 08c0c86a..d08e21fb 100644
--- a/package.json
+++ b/package.json
@@ -33,16 +33,17 @@
   "main": "./lib/index",
   "module": "./es/index",
   "scripts": {
-    "start": "father doc dev --storybook",
-    "build": "father doc build --storybook",
+    "start": "dumi dev",
+    "docs:build": "dumi build",
+    "docs:deploy": "gh-pages -d docs-dist",
     "compile": "father build && lessc assets/index.less assets/index.css",
     "prepublishOnly": "npm run compile && np --yolo --no-publish --any-branch",
     "postpublish": "npm run gh-pages",
-    "lint": "eslint src/ examples/ --ext .tsx,.ts,.jsx,.js",
+    "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
     "test": "father test",
     "coverage": "father test --coverage",
     "gh-pages": "npm run build && father doc deploy",
-    "now-build": "npm run build"
+    "now-build": "npm run docs:build"
   },
   "peerDependencies": {
     "react": "*",
@@ -55,11 +56,13 @@
     "@types/warning": "^3.0.0",
     "@umijs/fabric": "^2.4.8",
     "css-animation": "^1.2.0",
+    "dumi": "^1.1.4",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",
     "enzyme-to-json": "^3.0.0",
     "eslint": "^7.0.0",
     "father": "^2.29.8",
+    "glob": "^7.1.6",
     "less": "^3.11.1",
     "np": "^6.0.0",
     "rc-dialog": "^8.1.0",
diff --git a/tsconfig.json b/tsconfig.json
index 35ec7c4a..592bf940 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,7 +10,7 @@
     "paths": {
       "@/*": ["src/*"],
       "@@/*": ["src/.umi/*"],
-      "rc-table": ["src/index.ts"]
+      "rc-tree": ["src/index.ts"]
     }
   }
 }
diff --git a/typings.d.ts b/typings.d.ts
new file mode 100644
index 00000000..71e0e9f4
--- /dev/null
+++ b/typings.d.ts
@@ -0,0 +1,2 @@
+declare module '*.css';
+declare module '*.less';
diff --git a/update-example.js b/update-example.js
new file mode 100644
index 00000000..1924fffa
--- /dev/null
+++ b/update-example.js
@@ -0,0 +1,29 @@
+/*
+  用于 dumi 改造使用，
+  可用于将 examples 的文件批量修改为 demo 引入形式，
+  其他项目根据具体情况使用。
+*/
+
+const fs = require('fs');
+const glob = require('glob');
+
+const paths = glob.sync('./docs/examples/*.jsx');
+
+paths.forEach(path => {
+  const name = path.split('/').pop().split('.')[0];
+  fs.writeFile(
+    `./docs/demo/${name}.md`,
+    `## ${name}
+
+<code src="../examples/${name}.jsx">
+`,
+    'utf8',
+    function(error) {
+      if(error){
+        console.log(error);
+        return false;
+      }
+      console.log(`${name} 更新成功~`);
+    }
+  )
+});

From 8fcb035d3b4a75b0836aa0bc2d0da18b4d91e928 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Sat, 27 Feb 2021 21:30:35 +0800
Subject: [PATCH 170/397] fix: only allow cross level horziontal drop when
 physical drop node is the last child itself (#425)

* fix: only allow cross level horziontal drop when physical drop node is the last child itself

* fix: test

* chore: typo
---
 src/Tree.tsx       |  59 +++----
 src/util.tsx       | 124 ++++++++------
 tests/Tree.spec.js | 413 +++++++++------------------------------------
 3 files changed, 176 insertions(+), 420 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 18ad7cdf..4e691017 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -160,7 +160,7 @@ export interface TreeProps {
   virtual?: boolean;
 
   // direction for drag logic
-  direction?: Direction
+  direction?: Direction;
 }
 
 interface TreeState {
@@ -324,7 +324,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     } else if (!prevProps && props.defaultExpandAll) {
       const cloneKeyEntities = { ...keyEntities };
       delete cloneKeyEntities[MOTION_KEY];
-      newState.expandedKeys = Object.keys(cloneKeyEntities).map(key => cloneKeyEntities[key].key);
+      newState.expandedKeys = Object.keys(cloneKeyEntities).map((key) => cloneKeyEntities[key].key);
     } else if (!prevProps && props.defaultExpandedKeys) {
       newState.expandedKeys =
         props.autoExpandParent || props.defaultExpandParent
@@ -427,13 +427,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
    * But let's just keep it to avoid event trigger logic change.
    */
   onNodeDragEnter = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
-    const {
-      expandedKeys,
-      keyEntities,
-      dragChildrenKeys,
-      flattenNodes,
-      indent,
-    } = this.state;
+    const { expandedKeys, keyEntities, dragChildrenKeys, flattenNodes, indent } = this.state;
     const { onDragEnter, onExpand, allowDrop, direction } = this.props;
     const { pos } = node.props;
     const { dragNode } = this;
@@ -448,6 +442,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       dragOverNodeKey,
     } = calcDropPosition(
       event,
+      dragNode,
       node,
       indent,
       this.dragStartMousePosition,
@@ -481,7 +476,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     if (!this.delayedDragEnterLogic) {
       this.delayedDragEnterLogic = {};
     }
-    Object.keys(this.delayedDragEnterLogic).forEach(key => {
+    Object.keys(this.delayedDragEnterLogic).forEach((key) => {
       clearTimeout(this.delayedDragEnterLogic[key]);
     });
 
@@ -550,13 +545,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeDragOver = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
-    const {
-      dragChildrenKeys,
-      flattenNodes,
-      keyEntities,
-      expandedKeys,
-      indent,
-    } = this.state;
+    const { dragChildrenKeys, flattenNodes, keyEntities, expandedKeys, indent } = this.state;
     const { onDragOver, allowDrop, direction } = this.props;
     const { dragNode } = this;
 
@@ -570,6 +559,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       dragOverNodeKey,
     } = calcDropPosition(
       event,
+      dragNode,
       node,
       indent,
       this.dragStartMousePosition,
@@ -580,11 +570,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       direction,
     );
 
-    if (
-      !dragNode ||
-      dragChildrenKeys.indexOf(dropTargetKey) !== -1 ||
-      !dropAllowed
-    ) {
+    if (!dragNode || dragChildrenKeys.indexOf(dropTargetKey) !== -1 || !dropAllowed) {
       // don't allow drop inside its children
       // don't allow drop when drop is not allowed caculated by calcDropPosition
       return;
@@ -651,7 +637,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   // since stopPropagation() is called in treeNode
   // if onWindowDrag is called, whice means state is keeped, drag state should be cleared
-  onWindowDragEnd = event => {
+  onWindowDragEnd = (event) => {
     this.onNodeDragEnd(event, null, true);
     window.removeEventListener('dragend', this.onWindowDragEnd);
   };
@@ -693,13 +679,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
     if (dropTargetKey === null) return;
 
     const abstractDropNodeProps = {
-      ...getTreeNodeProps(
-        dropTargetKey,
-        this.getTreeNodeRequiredProps(),
-      ),
+      ...getTreeNodeProps(dropTargetKey, this.getTreeNodeRequiredProps()),
       active: this.getActiveItem()?.data.key === dropTargetKey,
       data: this.state.keyEntities[dropTargetKey].node,
-    }
+    };
     const dropToChild = dragChildrenKeys.indexOf(dropTargetKey) !== -1;
 
     warning(
@@ -773,13 +756,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
     // [Legacy] Not found related usage in doc or upper libs
     const selectedNodes = selectedKeys
-      .map(selectedKey => {
+      .map((selectedKey) => {
         const entity = keyEntities[selectedKey];
         if (!entity) return null;
 
         return entity.node;
       })
-      .filter(node => node);
+      .filter((node) => node);
 
     this.setUncontrolledState({ selectedKeys });
 
@@ -822,9 +805,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
       checkedObj = { checked: checkedKeys, halfChecked: halfCheckedKeys };
 
       eventObj.checkedNodes = checkedKeys
-        .map(checkedKey => keyEntities[checkedKey])
-        .filter(entity => entity)
-        .map(entity => entity.node);
+        .map((checkedKey) => keyEntities[checkedKey])
+        .filter((entity) => entity)
+        .map((entity) => entity.node);
 
       this.setUncontrolledState({ checkedKeys });
     } else {
@@ -853,7 +836,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       eventObj.checkedNodesPositions = [];
       eventObj.halfCheckedKeys = halfCheckedKeys;
 
-      checkedKeys.forEach(checkedKey => {
+      checkedKeys.forEach((checkedKey) => {
         const entity = keyEntities[checkedKey];
         if (!entity) return;
 
@@ -880,7 +863,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeLoad = (treeNode: EventDataNode) =>
-    new Promise<void>(resolve => {
+    new Promise<void>((resolve) => {
       // We need to get the latest state of loading/loaded keys
       this.setState(({ loadedKeys = [], loadingKeys = [] }): any => {
         const { loadData, onLoad } = this.props;
@@ -1116,7 +1099,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
+  onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
     const { activeKey, expandedKeys, checkedKeys } = this.state;
     const { onKeyDown, checkable, selectable } = this.props;
 
@@ -1218,7 +1201,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     let allPassed = true;
     const newState = {};
 
-    Object.keys(state).forEach(name => {
+    Object.keys(state).forEach((name) => {
       if (name in this.props) {
         allPassed = false;
         return;
@@ -1236,7 +1219,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  scrollTo: ScrollTo = scroll => {
+  scrollTo: ScrollTo = (scroll) => {
     this.listRef.current.scrollTo(scroll);
   };
 
diff --git a/src/util.tsx b/src/util.tsx
index fed0623c..3ed97f4a 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -6,7 +6,15 @@
 import React from 'react';
 import warning from 'rc-util/lib/warning';
 import TreeNode, { TreeNodeProps } from './TreeNode';
-import { NodeElement, Key, DataNode, DataEntity, NodeInstance, FlattenNode, Direction } from './interface';
+import {
+  NodeElement,
+  Key,
+  DataNode,
+  DataEntity,
+  NodeInstance,
+  FlattenNode,
+  Direction,
+} from './interface';
 import { TreeProps, AllowDrop } from './Tree';
 
 export function arrDel(list: Key[], value: Key) {
@@ -56,7 +64,7 @@ export function getDragChildrenKeys(dragNodeKey: Key, keyEntities: Record<Key, D
   return dragChildrenKeys;
 }
 
-export function isLastChild (treeNodeEntity: DataEntity) {
+export function isLastChild(treeNodeEntity: DataEntity) {
   if (treeNodeEntity.parent) {
     const posArr = posToArr(treeNodeEntity.pos);
     return Number(posArr[posArr.length - 1]) === treeNodeEntity.parent.children.length - 1;
@@ -64,7 +72,7 @@ export function isLastChild (treeNodeEntity: DataEntity) {
   return false;
 }
 
-export function isFirstChild (treeNodeEntity: DataEntity) {
+export function isFirstChild(treeNodeEntity: DataEntity) {
   const posArr = posToArr(treeNodeEntity.pos);
   return Number(posArr[posArr.length - 1]) === 0;
 }
@@ -72,11 +80,12 @@ export function isFirstChild (treeNodeEntity: DataEntity) {
 // Only used when drag, not affect SSR.
 export function calcDropPosition(
   event: React.MouseEvent,
+  dragNode: NodeInstance,
   targetNode: NodeInstance,
   indent: number,
   startMousePosition: {
-    x: number,
-    y: number,
+    x: number;
+    y: number;
   },
   allowDrop: AllowDrop,
   flattenedNodes: FlattenNode[],
@@ -84,18 +93,19 @@ export function calcDropPosition(
   expandKeys: Key[],
   direction: Direction,
 ): {
-  dropPosition: -1 | 0 | 1,
-  dropLevelOffset: number,
-  dropTargetKey: Key,
-  dropTargetPos: string,
-  dropContainerKey: Key,
-  dragOverNodeKey: Key,
-  dropAllowed: boolean,
+  dropPosition: -1 | 0 | 1;
+  dropLevelOffset: number;
+  dropTargetKey: Key;
+  dropTargetPos: string;
+  dropContainerKey: Key;
+  dragOverNodeKey: Key;
+  dropAllowed: boolean;
 } {
   const { clientX, clientY } = event;
   const { top, height } = (event.target as HTMLElement).getBoundingClientRect();
   // optional chain for testing
-  const horizontalMouseOffset = (direction === 'rtl' ? -1 : 1) * ((startMousePosition?.x || 0) - clientX);
+  const horizontalMouseOffset =
+    (direction === 'rtl' ? -1 : 1) * ((startMousePosition?.x || 0) - clientX);
   const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent;
 
   // find abstract drop node by horizontal offset
@@ -104,30 +114,34 @@ export function calcDropPosition(
   if (clientY < top + height / 2) {
     // first half, set abstract drop node to previous node
     const nodeIndex = flattenedNodes.findIndex(
-      flattenedNode => flattenedNode.data.key === abstractDropNodeEntity.key,
+      (flattenedNode) => flattenedNode.data.key === abstractDropNodeEntity.key,
     );
     const prevNodeIndex = nodeIndex <= 0 ? 0 : nodeIndex - 1;
     const prevNodeKey = flattenedNodes[prevNodeIndex].data.key;
     abstractDropNodeEntity = keyEntities[prevNodeKey];
   }
 
+  const initialAbstractDropNodeKey = abstractDropNodeEntity.key;
+
   const abstractDragOverEntity = abstractDropNodeEntity;
   const dragOverNodeKey = abstractDropNodeEntity.key;
 
   let dropPosition: -1 | 0 | 1 = 0;
   let dropLevelOffset = 0;
-  for (let i = 0; i < rawDropLevelOffset; i += 1) {
-    if (
-      isLastChild(abstractDropNodeEntity)
-    ) {
-      abstractDropNodeEntity = abstractDropNodeEntity.parent;
-      dropLevelOffset += 1;
-    } else {
-      break;
+
+  // Only allow cross level drop when dragging on the last child itself
+  if (initialAbstractDropNodeKey === dragNode.props.eventKey) {
+    for (let i = 0; i < rawDropLevelOffset; i += 1) {
+      if (isLastChild(abstractDropNodeEntity)) {
+        abstractDropNodeEntity = abstractDropNodeEntity.parent;
+        dropLevelOffset += 1;
+      } else {
+        break;
+      }
     }
   }
 
-  const abstractDropDataNode = abstractDropNodeEntity.node
+  const abstractDropDataNode = abstractDropNodeEntity.node;
   let dropAllowed = true;
   if (
     isFirstChild(abstractDropNodeEntity) &&
@@ -140,33 +154,35 @@ export function calcDropPosition(
     abstractDropNodeEntity.key === targetNode.props.eventKey
   ) {
     // first half of first node in first level
-    dropPosition = -1
+    dropPosition = -1;
   } else if (
     (abstractDragOverEntity.children || []).length &&
     expandKeys.includes(dragOverNodeKey)
   ) {
     // drop on expanded node
     // only allow drop inside
-    if (allowDrop({
-      dropNode: abstractDropDataNode,
-      dropPosition: 0,
-    })) {
+    if (
+      allowDrop({
+        dropNode: abstractDropDataNode,
+        dropPosition: 0,
+      })
+    ) {
       dropPosition = 0;
     } else {
-      dropAllowed = false
+      dropAllowed = false;
     }
-  } else if (
-    dropLevelOffset === 0
-  ) {
+  } else if (dropLevelOffset === 0) {
     if (rawDropLevelOffset > -1.5) {
       // | Node     | <- abstractDropNode
       // | -^-===== | <- mousePosition
       // 1. try drop after
       // 2. do not allow drop
-      if (allowDrop({
-        dropNode: abstractDropDataNode,
-        dropPosition: 1,
-      })) {
+      if (
+        allowDrop({
+          dropNode: abstractDropDataNode,
+          dropPosition: 1,
+        })
+      ) {
         dropPosition = 1;
       } else {
         dropAllowed = false;
@@ -179,15 +195,19 @@ export function calcDropPosition(
       // 1. try drop inside
       // 2. try drop after
       // 3. do not allow drop
-      if (allowDrop({
-        dropNode: abstractDropDataNode,
-        dropPosition: 0,
-      })) {
+      if (
+        allowDrop({
+          dropNode: abstractDropDataNode,
+          dropPosition: 0,
+        })
+      ) {
         dropPosition = 0;
-      } else if (allowDrop({
-        dropNode: abstractDropDataNode,
-        dropPosition: 1,
-      })) {
+      } else if (
+        allowDrop({
+          dropNode: abstractDropDataNode,
+          dropPosition: 1,
+        })
+      ) {
         dropPosition = 1;
       } else {
         dropAllowed = false;
@@ -199,10 +219,12 @@ export function calcDropPosition(
     // --^--|----=====| <- mousePosition
     // 1. try insert after Node1
     // 2. do not allow drop
-    if (allowDrop({
-      dropNode: abstractDropDataNode,
-      dropPosition: 1,
-    })) {
+    if (
+      allowDrop({
+        dropNode: abstractDropDataNode,
+        dropPosition: 1,
+      })
+    ) {
       dropPosition = 1;
     } else {
       dropAllowed = false;
@@ -215,7 +237,7 @@ export function calcDropPosition(
     dropTargetKey: abstractDropNodeEntity.key,
     dropTargetPos: abstractDropNodeEntity.pos,
     dragOverNodeKey,
-    dropContainerKey: dropPosition === 0 ? null : (abstractDropNodeEntity.parent?.key || null),
+    dropContainerKey: dropPosition === 0 ? null : abstractDropNodeEntity.parent?.key || null,
     dropAllowed,
   };
 }
@@ -312,7 +334,7 @@ export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, Dat
     }
   }
 
-  (keyList || []).forEach(key => {
+  (keyList || []).forEach((key) => {
     conductUp(key);
   });
 
@@ -324,7 +346,7 @@ export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, Dat
  */
 export function getDataAndAria(props: Partial<TreeProps | TreeNodeProps>) {
   const omitProps: Record<string, string> = {};
-  Object.keys(props).forEach(key => {
+  Object.keys(props).forEach((key) => {
     if (key.startsWith('data-') || key.startsWith('aria-')) {
       omitProps[key] = props[key];
     }
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 54c7bc89..e40b60c3 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -15,8 +15,8 @@ const OPEN_CLASSNAME = '.rc-tree-switcher_open';
 const CHECKED_CLASSNAME = '.rc-tree-checkbox-checked';
 const SELECTED_CLASSNAME = '.rc-tree-node-selected';
 
-const delay = timeout =>
-  new Promise(resolve => {
+const delay = (timeout) =>
+  new Promise((resolve) => {
     setTimeout(resolve, timeout);
   });
 
@@ -181,12 +181,7 @@ describe('Tree Basic', () => {
       );
       const switcher = wrapper.find('.rc-tree-switcher');
 
-      let node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
+      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
       switcher.simulate('click');
       expect(handleExpand).toHaveBeenCalledWith(['0-0'], {
         expanded: true,
@@ -194,12 +189,7 @@ describe('Tree Basic', () => {
         nativeEvent: expect.anything(),
       });
 
-      node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
+      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
       switcher.simulate('click');
       expect(handleExpand).toHaveBeenCalledWith([], {
         expanded: false,
@@ -232,7 +222,7 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
+      wrapper.find('.rc-tree-checkbox').forEach((checkbox) => {
         expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
       });
     });
@@ -276,20 +266,9 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .at(1)
-        .simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .at(2)
-        .simulate('click');
-      expect(
-        wrapper
-          .find('.rc-tree-checkbox')
-          .first()
-          .is(CHECKED_CLASSNAME),
-      ).toBe(true);
+      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
+      wrapper.find('.rc-tree-checkbox').at(2).simulate('click');
+      expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
     });
 
     it('turns parent node to half checked when child is checked', () => {
@@ -302,16 +281,10 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .last()
-        .simulate('click');
-      expect(
-        wrapper
-          .find('.rc-tree-checkbox')
-          .first()
-          .is('.rc-tree-checkbox-indeterminate'),
-      ).toBe(true);
+      wrapper.find('.rc-tree-checkbox').last().simulate('click');
+      expect(wrapper.find('.rc-tree-checkbox').first().is('.rc-tree-checkbox-indeterminate')).toBe(
+        true,
+      );
     });
 
     it('turns parent node to checked if it is half checked', () => {
@@ -324,15 +297,9 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .last()
-        .simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
+      wrapper.find('.rc-tree-checkbox').last().simulate('click');
+      wrapper.find('.rc-tree-checkbox').first().simulate('click');
+      wrapper.find('.rc-tree-checkbox').forEach((checkbox) => {
         expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
       });
     });
@@ -351,16 +318,8 @@ describe('Tree Basic', () => {
       const nodeData00 = { title: 'parent 1', key: '0-0' };
       const nodeData000 = { title: 'leaf 1', key: '0-0-0' };
 
-      let node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click');
+      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
+      wrapper.find('.rc-tree-checkbox').first().simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         ['0-0', '0-0-0'],
         objectMatcher({
@@ -377,16 +336,8 @@ describe('Tree Basic', () => {
         }),
       );
 
-      node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .last()
-          .props(),
-      );
-      wrapper
-        .find('.rc-tree-checkbox')
-        .last()
-        .simulate('click');
+      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).last().props());
+      wrapper.find('.rc-tree-checkbox').last().simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         [],
         objectMatcher({
@@ -411,10 +362,7 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      wrapper
-        .find('.rc-tree-checkbox')
-        .at(1)
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
       wrapper.setProps({
         children: (
           <TreeNode title="parent 1" key="0-0">
@@ -424,12 +372,7 @@ describe('Tree Basic', () => {
           </TreeNode>
         ),
       });
-      expect(() =>
-        wrapper
-          .find('.rc-tree-checkbox')
-          .at(2)
-          .simulate('click'),
-      ).not.toThrow();
+      expect(() => wrapper.find('.rc-tree-checkbox').at(2).simulate('click')).not.toThrow();
     });
     // https://github.com/react-component/tree/issues/90
     it('check works correctly after adding children dynamically', () => {
@@ -440,10 +383,7 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      wrapper
-        .find('.rc-tree-checkbox')
-        .at(1)
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
       wrapper.setProps({
         children: (
           <TreeNode title="parent 1" key="0-0">
@@ -452,18 +392,13 @@ describe('Tree Basic', () => {
           </TreeNode>
         ),
       });
-      expect(() =>
-        wrapper
-          .find('.rc-tree-checkbox')
-          .at(2)
-          .simulate('click'),
-      ).not.toThrow();
+      expect(() => wrapper.find('.rc-tree-checkbox').at(2).simulate('click')).not.toThrow();
     });
 
     // https://github.com/ant-design/ant-design/issues/7353
     it('check children after changing from children[disableCheckbox] from true to false', () => {
       let checkedKeys = null;
-      const mockHandleCheck = keys => (checkedKeys = keys);
+      const mockHandleCheck = (keys) => (checkedKeys = keys);
       function Test({ disableCheckbox }) {
         return (
           <Tree checkable onCheck={mockHandleCheck}>
@@ -476,17 +411,10 @@ describe('Tree Basic', () => {
         );
       }
       const wrapper = mount(<Test disableCheckbox />);
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').first().simulate('click');
       expect(checkedKeys).toEqual(['0-0']);
       wrapper.setProps({ disableCheckbox: false });
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click')
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').first().simulate('click').simulate('click');
       expect(checkedKeys).toEqual(['0-0', '0-0-1', '0-0-2', '0-0-3']);
     });
 
@@ -526,10 +454,7 @@ describe('Tree Basic', () => {
         }
       }
       const wrapper = mount(<Test />);
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').first().simulate('click');
 
       expect(renderToJson(wrapper.render())).toMatchSnapshot();
     });
@@ -566,10 +491,7 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      wrapper
-        .find('.rc-tree-checkbox')
-        .last()
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').last().simulate('click');
 
       expect(onCheck.mock.calls[0][0].sort()).toEqual(['0-0', '0-0-1']);
     });
@@ -584,22 +506,9 @@ describe('Tree Basic', () => {
           </Tree>,
         );
         wrapper.find('.rc-tree-switcher').simulate('click');
-        wrapper
-          .find('.rc-tree-checkbox')
-          .first()
-          .simulate('click');
-        expect(
-          wrapper
-            .find('.rc-tree-checkbox')
-            .first()
-            .is(CHECKED_CLASSNAME),
-        ).toBe(true);
-        expect(
-          wrapper
-            .find('.rc-tree-checkbox')
-            .last()
-            .is(CHECKED_CLASSNAME),
-        ).toBe(false);
+        wrapper.find('.rc-tree-checkbox').first().simulate('click');
+        expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
+        expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
       });
 
       describe('controlled mode', () => {
@@ -611,7 +520,7 @@ describe('Tree Basic', () => {
             },
           };
 
-          handleCheck = checkedKeys => {
+          handleCheck = (checkedKeys) => {
             this.setState({ checkedKeys });
           };
 
@@ -634,22 +543,9 @@ describe('Tree Basic', () => {
         it('do not check children', () => {
           const wrapper = mount(<App />);
           wrapper.find('.rc-tree-switcher').simulate('click');
-          wrapper
-            .find('.rc-tree-checkbox')
-            .first()
-            .simulate('click');
-          expect(
-            wrapper
-              .find('.rc-tree-checkbox')
-              .first()
-              .is(CHECKED_CLASSNAME),
-          ).toBe(true);
-          expect(
-            wrapper
-              .find('.rc-tree-checkbox')
-              .last()
-              .is(CHECKED_CLASSNAME),
-          ).toBe(false);
+          wrapper.find('.rc-tree-checkbox').first().simulate('click');
+          expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
+          expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
         });
 
         it('do not uncheck parent', () => {
@@ -658,22 +554,9 @@ describe('Tree Basic', () => {
             checkedKeys: { checked: ['0-0', '0-0-0'], halfChecked: [] },
           });
           wrapper.find('.rc-tree-switcher').simulate('click');
-          wrapper
-            .find('.rc-tree-checkbox')
-            .last()
-            .simulate('click');
-          expect(
-            wrapper
-              .find('.rc-tree-checkbox')
-              .first()
-              .is(CHECKED_CLASSNAME),
-          ).toBe(true);
-          expect(
-            wrapper
-              .find('.rc-tree-checkbox')
-              .last()
-              .is(CHECKED_CLASSNAME),
-          ).toBe(false);
+          wrapper.find('.rc-tree-checkbox').last().simulate('click');
+          expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
+          expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
         });
       });
     });
@@ -698,12 +581,7 @@ describe('Tree Basic', () => {
 
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
       expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(
-        wrapper
-          .state()
-          .checkedKeys.slice()
-          .sort(),
-      ).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
+      expect(wrapper.state().checkedKeys.slice().sort()).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
@@ -725,22 +603,12 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      const firstNode = () =>
-        wrapper
-          .find(TreeNode)
-          .first()
-          .find('.rc-tree-checkbox')
-          .first();
+      const firstNode = () => wrapper.find(TreeNode).first().find('.rc-tree-checkbox').first();
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
       expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(
-        wrapper
-          .state()
-          .checkedKeys.slice()
-          .sort(),
-      ).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
+      expect(wrapper.state().checkedKeys.slice().sort()).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
@@ -762,18 +630,8 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      const getParent = () =>
-        wrapper
-          .find(TreeNode)
-          .at(1)
-          .find('.rc-tree-checkbox')
-          .first();
-      const getNode = () =>
-        wrapper
-          .find(TreeNode)
-          .at(2)
-          .find('.rc-tree-checkbox')
-          .first();
+      const getParent = () => wrapper.find(TreeNode).at(1).find('.rc-tree-checkbox').first();
+      const getNode = () => wrapper.find(TreeNode).at(2).find('.rc-tree-checkbox').first();
 
       getNode().simulate('click');
       expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
@@ -822,12 +680,7 @@ describe('Tree Basic', () => {
       );
       const nodeContent = wrapper.find('.rc-tree-node-content-wrapper');
 
-      let node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
+      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
       nodeContent.simulate('click');
       expect(handleSelect).toHaveBeenCalledWith(
         ['0-0'],
@@ -840,12 +693,7 @@ describe('Tree Basic', () => {
         }),
       );
 
-      node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
+      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
       nodeContent.simulate('click');
       expect(handleSelect).toHaveBeenCalledWith(
         [],
@@ -875,16 +723,8 @@ describe('Tree Basic', () => {
       const dataNode1 = { title: 'parent 1', key: '0-0' };
       const dataNode2 = { title: 'leaf 1', key: '0-0-0' };
 
-      let node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
-      wrapper
-        .find('.rc-tree-node-content-wrapper')
-        .first()
-        .simulate('click');
+      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
+      wrapper.find('.rc-tree-node-content-wrapper').first().simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         ['0-0', '0-0-0'],
         objectMatcher({
@@ -901,16 +741,8 @@ describe('Tree Basic', () => {
         }),
       );
 
-      node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .last()
-          .props(),
-      );
-      wrapper
-        .find('.rc-tree-node-content-wrapper')
-        .last()
-        .simulate('click');
+      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).last().props());
+      wrapper.find('.rc-tree-node-content-wrapper').last().simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         [],
         objectMatcher({
@@ -997,12 +829,7 @@ describe('Tree Basic', () => {
       </Tree>,
     );
 
-    expect(
-      wrapper
-        .find(InternalTreeNode)
-        .find('div')
-        .is('.filter-node'),
-    ).toBe(true);
+    expect(wrapper.find(InternalTreeNode).find('div').is('.filter-node')).toBe(true);
   });
 
   it('loads nodes asynchronously', () => {
@@ -1035,12 +862,7 @@ describe('Tree Basic', () => {
       const onDragStart = jest.fn();
       const wrapper = mount(createTree({ onDragStart }));
       const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(1)
-          .props(),
-      );
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
       treeNode.simulate('dragStart');
       const event = onDragStart.mock.calls[0][0];
       expect(event.node).toEqual(node);
@@ -1057,20 +879,12 @@ describe('Tree Basic', () => {
       await delay(900);
       expect(onDragEnter).not.toHaveBeenCalled();
 
-      wrapper
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragEnter');
+      wrapper.find('.dropTarget').at(0).simulate('dragEnter');
       expect(onDragEnter).toHaveBeenCalled();
 
       await delay(900);
       wrapper.update();
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .props(),
-      );
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
       const event = onDragEnter.mock.calls[0][0];
       expect(event.node.key).toEqual(node.key);
       expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
@@ -1080,17 +894,9 @@ describe('Tree Basic', () => {
     it('fires dragOver event', () => {
       const onDragOver = jest.fn();
       const wrapper = mount(createTree({ onDragOver }));
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .props(),
-      );
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      wrapper
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragOver');
+      wrapper.find('.dropTarget').at(0).simulate('dragOver');
       const event = onDragOver.mock.calls[0][0];
       expect(event.node).toEqual(node);
     });
@@ -1098,16 +904,8 @@ describe('Tree Basic', () => {
     it('fires dragLeave event', () => {
       const onDragLeave = jest.fn();
       const wrapper = mount(createTree({ onDragLeave }));
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .props(),
-      );
-      wrapper
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragLeave');
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+      wrapper.find('.dropTarget').at(0).simulate('dragLeave');
       const event = onDragLeave.mock.calls[0][0];
       expect(event.node).toEqual(node);
     });
@@ -1118,22 +916,9 @@ describe('Tree Basic', () => {
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
       wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
       wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
-      const dropNode = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .props(),
-      );
-      const dragNode = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(1)
-          .props(),
-      );
-      wrapper
-        .find('.dropTarget')
-        .at(0)
-        .simulate('drop');
+      const dropNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+      const dragNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
+      wrapper.find('.dropTarget').at(0).simulate('drop');
       const event = onDrop.mock.calls[0][0];
       expect(event.node).toEqual(dropNode);
       expect(event.dragNode).toEqual(dragNode);
@@ -1143,12 +928,7 @@ describe('Tree Basic', () => {
     it('fires dropEnd event', () => {
       const onDragEnd = jest.fn();
       const wrapper = mount(createTree({ onDragEnd }));
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(1)
-          .props(),
-      );
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
       const event = onDragEnd.mock.calls[0][0];
       expect(event.node).toEqual(node);
@@ -1166,23 +946,14 @@ describe('Tree Basic', () => {
       const normalTree = wrapper.find(Tree).at(1);
 
       dragTree.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      normalTree
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragEnter');
-      normalTree
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragOver');
-      normalTree
-        .find('.dropTarget')
-        .at(0)
-        .simulate('drop');
+      normalTree.find('.dropTarget').at(0).simulate('dragEnter');
+      normalTree.find('.dropTarget').at(0).simulate('dragOver');
+      normalTree.find('.dropTarget').at(0).simulate('drop');
     });
 
     describe('full steps', () => {
       function dropTarget(targetSelector) {
-        return new Promise(resolve => {
+        return new Promise((resolve) => {
           const wrapper = mount(
             <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
               <TreeNode key="0-0" className="dragTarget">
@@ -1279,7 +1050,7 @@ describe('Tree Basic', () => {
       afterEach(() => {
         domSpy.mockRestore();
       });
-      ['ltr', 'rtl'].forEach(dir => {
+      ['ltr', 'rtl'].forEach((dir) => {
         const base = dir === 'ltr' ? 1 : -1;
         it('allowDrop all nodes', () => {
           const onDrop = jest.fn();
@@ -1311,8 +1082,9 @@ describe('Tree Basic', () => {
             clientY: 600,
           });
           wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
-          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
+          // (in ltr) drag from right to left, should be insert after, so drop position is 1
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
           wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
             clientX: base * 500,
             clientY: 500,
@@ -1642,8 +1414,9 @@ describe('Tree Basic', () => {
             clientY: 600,
           });
           wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
-          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
+          // (in ltr) drag from right to left, should be insert after, so drop position is 1
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
           wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
             clientX: base * 500,
             clientY: 500,
@@ -1698,9 +1471,9 @@ describe('Tree Basic', () => {
     expect(() => {
       mount(
         <Tree>
-          {[0, 1].map(i => (
+          {[0, 1].map((i) => (
             <TreeNode title={i} key={i}>
-              {[2, 3].map(j => (
+              {[2, 3].map((j) => (
                 <TreeNode title={j} key={`${i}_${j}`} />
               ))}
               <TreeNode title="4" key={`${i}_4`} />
@@ -1715,7 +1488,7 @@ describe('Tree Basic', () => {
     const wrapper = render(
       <Tree>
         <TreeNode title="0" key="0">
-          {[1, 2].map(i => (
+          {[1, 2].map((i) => (
             <TreeNode title={i} key={i} />
           ))}
           <TreeNode title="3" key="3" />
@@ -1773,7 +1546,7 @@ describe('Tree Basic', () => {
           checkable
           defaultExpandAll
           checkedKeys={checkedKeys}
-          onCheck={keys => {
+          onCheck={(keys) => {
             setCheckedKeys(keys);
             onCheck(keys);
           }}
@@ -1785,17 +1558,11 @@ describe('Tree Basic', () => {
     const wrapper = mount(<Demo />);
     wrapper.update();
 
-    wrapper
-      .find('.rc-tree-checkbox')
-      .first()
-      .simulate('click');
+    wrapper.find('.rc-tree-checkbox').first().simulate('click');
     expect(onCheck).toHaveBeenCalledWith([11, 22]);
 
     onCheck.mockReset();
-    wrapper
-      .find('.rc-tree-checkbox')
-      .last()
-      .simulate('click');
+    wrapper.find('.rc-tree-checkbox').last().simulate('click');
     expect(onCheck).toHaveBeenCalledWith([]);
   });
 
@@ -1855,10 +1622,7 @@ describe('Tree Basic', () => {
     wrapper.setProps({ expandedKeys: undefined });
 
     // Click should not crash
-    wrapper
-      .find('.rc-tree-switcher')
-      .first()
-      .simulate('click');
+    wrapper.find('.rc-tree-switcher').first().simulate('click');
   });
 
   it('controlled should block expanded', () => {
@@ -1869,10 +1633,7 @@ describe('Tree Basic', () => {
       />,
     );
 
-    wrapper
-      .find('.rc-tree-switcher')
-      .first()
-      .simulate('click');
+    wrapper.find('.rc-tree-switcher').first().simulate('click');
     expect(wrapper.find('List').props().data).toHaveLength(2);
   });
 
@@ -1903,23 +1664,13 @@ describe('Tree Basic', () => {
 
     // trigger click to expand node
     wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(
-      wrapper
-        .find('.rc-tree-switcher')
-        .first()
-        .is(OPEN_CLASSNAME),
-    ).toBe(true);
+    expect(wrapper.find('.rc-tree-switcher').first().is(OPEN_CLASSNAME)).toBe(true);
     expect(onExpand).toBeCalled();
 
     // click again
     onExpand.mockReset();
     wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(
-      wrapper
-        .find('.rc-tree-switcher')
-        .first()
-        .is(OPEN_CLASSNAME),
-    ).toBe(true);
+    expect(wrapper.find('.rc-tree-switcher').first().is(OPEN_CLASSNAME)).toBe(true);
     expect(onExpand).not.toHaveBeenCalled();
   });
 });

From e1032047e854586e2fa2b5b7e70c68fdfab2b62b Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sat, 27 Feb 2021 21:48:32 +0800
Subject: [PATCH 171/397] 4.1.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d08e21fb..a24c1675 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.1.1",
+  "version": "4.1.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From f220492c03df3c0b31daae0f0da1d90aef079ec2 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sat, 27 Feb 2021 21:55:07 +0800
Subject: [PATCH 172/397] chore: Fix script

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a24c1675..4ee3a589 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
     "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
     "test": "father test",
     "coverage": "father test --coverage",
-    "gh-pages": "npm run build && father doc deploy",
+    "gh-pages": "npm run docs:build && father doc deploy",
     "now-build": "npm run docs:build"
   },
   "peerDependencies": {

From f11ced3cc2085dad637165f6cf838f99ffb67145 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sat, 6 Mar 2021 10:05:58 +0800
Subject: [PATCH 173/397] fix: Check dragNode before process

---
 src/util.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/util.tsx b/src/util.tsx
index 3ed97f4a..89f962f6 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -130,7 +130,7 @@ export function calcDropPosition(
   let dropLevelOffset = 0;
 
   // Only allow cross level drop when dragging on the last child itself
-  if (initialAbstractDropNodeKey === dragNode.props.eventKey) {
+  if (dragNode && initialAbstractDropNodeKey === dragNode.props.eventKey) {
     for (let i = 0; i < rawDropLevelOffset; i += 1) {
       if (isLastChild(abstractDropNodeEntity)) {
         abstractDropNodeEntity = abstractDropNodeEntity.parent;

From 82f7f238b5018f8082f01ee59f7624d37cb73986 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sat, 6 Mar 2021 10:07:24 +0800
Subject: [PATCH 174/397] 4.1.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4ee3a589..65c027e9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.1.2",
+  "version": "4.1.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From 1c1e9773e7b9efdbd8020e45f2fe64f421b2d115 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Mon, 8 Mar 2021 16:44:16 +0800
Subject: [PATCH 175/397] fix: Only allow cross level drop when dragging on a
 non-expanded node (#431)

* fix: Only allow cross level drop when dragging on a non-expanded node

* test
---
 src/util.tsx       |  4 ++--
 tests/Tree.spec.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 52 insertions(+), 2 deletions(-)

diff --git a/src/util.tsx b/src/util.tsx
index 89f962f6..ba1458f0 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -129,8 +129,8 @@ export function calcDropPosition(
   let dropPosition: -1 | 0 | 1 = 0;
   let dropLevelOffset = 0;
 
-  // Only allow cross level drop when dragging on the last child itself
-  if (dragNode && initialAbstractDropNodeKey === dragNode.props.eventKey) {
+  // Only allow cross level drop when dragging on a non-expanded node
+  if (!expandKeys.includes(initialAbstractDropNodeKey)) {
     for (let i = 0; i < rawDropLevelOffset; i += 1) {
       if (isLastChild(abstractDropNodeEntity)) {
         abstractDropNodeEntity = abstractDropNodeEntity.parent;
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index e40b60c3..36313102 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1052,6 +1052,56 @@ describe('Tree Basic', () => {
       });
       ['ltr', 'rtl'].forEach((dir) => {
         const base = dir === 'ltr' ? 1 : -1;
+        it('not allow cross level dnd on expanded nodes', () => {
+          const onDrop = jest.fn();
+          const wrapper = mount(
+            <Tree draggable onDrop={onDrop} direction={dir} expandedKeys={['0-0', '0-1', '0-1-0']}>
+              <TreeNode key="0-0">
+                <TreeNode key="0-0-0" className="dropTarget1">
+                  <TreeNode key="0-0-0-0" />
+                </TreeNode>
+              </TreeNode>
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0" className="dropTarget2">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
+              </TreeNode>
+              <TreeNode key="0-2" className="dragTarget" />
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('drop');
+          // insert after 0-0, since 0-0-0 is not expanded
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0');
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('drop');
+          // insert into 0-1-0, since it is expanded, do not allow cross level dnd
+          expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0');
+          expect(onDrop.mock.calls[1][0].dropPosition).toEqual(0);
+        });
         it('allowDrop all nodes', () => {
           const onDrop = jest.fn();
           const wrapper = mount(

From c6c1a74b3aef5a8977ba783fd78ba37dc2f2c7a7 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 8 Mar 2021 16:47:38 +0800
Subject: [PATCH 176/397] 4.1.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 65c027e9..b2d5c570 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.1.3",
+  "version": "4.1.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From c19e7b2cd4b6b11fb1022044c12c53b5664365c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E5=B8=85?= <qixian.cs@outlook.com>
Date: Wed, 10 Mar 2021 12:11:39 +0800
Subject: [PATCH 177/397] support no render switcherIcon (#432)

* support no render switcherIcon

* add test

* less code

* fix test

* fix test
---
 docs/examples/custom-switch-icon.jsx |   3 +
 src/TreeNode.tsx                     |  42 ++-
 tests/Tree.spec.js                   | 419 ++++++++++++++++++++++-----
 3 files changed, 374 insertions(+), 90 deletions(-)

diff --git a/docs/examples/custom-switch-icon.jsx b/docs/examples/custom-switch-icon.jsx
index 5dfba3a3..e783d608 100644
--- a/docs/examples/custom-switch-icon.jsx
+++ b/docs/examples/custom-switch-icon.jsx
@@ -41,6 +41,9 @@ class Demo extends React.Component {
 
   render() {
     const switcherIcon = obj => {
+      if (obj.data.key?.startsWith('0-0-3')) {
+        return false;
+      }
       if (obj.isLeaf) {
         return getSvgIcon(
           arrowPath,
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index f2f0f3f8..9df145a5 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -317,36 +317,50 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     return treeSelectable;
   }
 
-  // Switcher
-  renderSwitcher = () => {
-    const { expanded, switcherIcon: switcherIconFromProps } = this.props;
+  renderSwitcherIconDom = (isLeaf: boolean) => {
+    const { switcherIcon: switcherIconFromProps } = this.props;
     const {
-      context: { prefixCls, switcherIcon: switcherIconFromCtx },
+      context: { switcherIcon: switcherIconFromCtx },
     } = this.props;
 
     const switcherIcon = switcherIconFromProps || switcherIconFromCtx;
+    // if switcherIconDom is null, no render switcher span
+    if (typeof switcherIcon === 'function') {
+      return switcherIcon({ ...this.props, isLeaf });
+    }
+    return switcherIcon;
+  };
+
+  // Switcher
+  renderSwitcher = () => {
+    const { expanded } = this.props;
+    const {
+      context: { prefixCls },
+    } = this.props;
 
     if (this.isLeaf()) {
-      return (
+      // if switcherIconDom is null, no render switcher span
+      const switcherIconDom = this.renderSwitcherIconDom(true);
+
+      return switcherIconDom !== false ? (
         <span className={classNames(`${prefixCls}-switcher`, `${prefixCls}-switcher-noop`)}>
-          {typeof switcherIcon === 'function'
-            ? switcherIcon({ ...this.props, isLeaf: true })
-            : switcherIcon}
+          {switcherIconDom}
         </span>
-      );
+      ) : null;
     }
 
     const switcherCls = classNames(
       `${prefixCls}-switcher`,
       `${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`,
     );
-    return (
+
+    const switcherIconDom = this.renderSwitcherIconDom(false);
+
+    return switcherIconDom !== false ? (
       <span onClick={this.onExpand} className={switcherCls}>
-        {typeof switcherIcon === 'function'
-          ? switcherIcon({ ...this.props, isLeaf: false })
-          : switcherIcon}
+        {switcherIconDom}
       </span>
-    );
+    ) : null;
   };
 
   // Checkbox
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 36313102..93845f55 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -15,8 +15,8 @@ const OPEN_CLASSNAME = '.rc-tree-switcher_open';
 const CHECKED_CLASSNAME = '.rc-tree-checkbox-checked';
 const SELECTED_CLASSNAME = '.rc-tree-node-selected';
 
-const delay = (timeout) =>
-  new Promise((resolve) => {
+const delay = timeout =>
+  new Promise(resolve => {
     setTimeout(resolve, timeout);
   });
 
@@ -52,6 +52,22 @@ describe('Tree Basic', () => {
     expect(renderToJson(wrapper)).toMatchSnapshot();
   });
 
+  it('switcherIcon = null, no render rc-tree-switcher null', () => {
+    const wrapper = mount(
+      <Tree className="forTest" defaultExpandAll switcherIcon={() => false}>
+        <TreeNode title="parent 1" key="0-0" className="spe">
+          <TreeNode title="leaf 1" key="0-0-0" disabled>
+            <TreeNode title="leaf" key="random" />
+            <TreeNode title="leaf" />
+          </TreeNode>
+          <TreeNode title="leaf 2" key="0-0-1" disableCheckbox />
+        </TreeNode>
+      </Tree>,
+    );
+    wrapper.update();
+    expect(wrapper.find('.rc-tree-switcher').exists()).toBeFalsy();
+  });
+
   describe('expanded', () => {
     it('expands all keys', () => {
       const wrapper = mount(
@@ -181,7 +197,12 @@ describe('Tree Basic', () => {
       );
       const switcher = wrapper.find('.rc-tree-switcher');
 
-      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
+      let node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       switcher.simulate('click');
       expect(handleExpand).toHaveBeenCalledWith(['0-0'], {
         expanded: true,
@@ -189,7 +210,12 @@ describe('Tree Basic', () => {
         nativeEvent: expect.anything(),
       });
 
-      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
+      node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       switcher.simulate('click');
       expect(handleExpand).toHaveBeenCalledWith([], {
         expanded: false,
@@ -222,7 +248,7 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach((checkbox) => {
+      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
         expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
       });
     });
@@ -266,9 +292,20 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
-      wrapper.find('.rc-tree-checkbox').at(2).simulate('click');
-      expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
+      wrapper
+        .find('.rc-tree-checkbox')
+        .at(1)
+        .simulate('click');
+      wrapper
+        .find('.rc-tree-checkbox')
+        .at(2)
+        .simulate('click');
+      expect(
+        wrapper
+          .find('.rc-tree-checkbox')
+          .first()
+          .is(CHECKED_CLASSNAME),
+      ).toBe(true);
     });
 
     it('turns parent node to half checked when child is checked', () => {
@@ -281,10 +318,16 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').last().simulate('click');
-      expect(wrapper.find('.rc-tree-checkbox').first().is('.rc-tree-checkbox-indeterminate')).toBe(
-        true,
-      );
+      wrapper
+        .find('.rc-tree-checkbox')
+        .last()
+        .simulate('click');
+      expect(
+        wrapper
+          .find('.rc-tree-checkbox')
+          .first()
+          .is('.rc-tree-checkbox-indeterminate'),
+      ).toBe(true);
     });
 
     it('turns parent node to checked if it is half checked', () => {
@@ -297,9 +340,15 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').last().simulate('click');
-      wrapper.find('.rc-tree-checkbox').first().simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach((checkbox) => {
+      wrapper
+        .find('.rc-tree-checkbox')
+        .last()
+        .simulate('click');
+      wrapper
+        .find('.rc-tree-checkbox')
+        .first()
+        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
         expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
       });
     });
@@ -318,8 +367,16 @@ describe('Tree Basic', () => {
       const nodeData00 = { title: 'parent 1', key: '0-0' };
       const nodeData000 = { title: 'leaf 1', key: '0-0-0' };
 
-      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
-      wrapper.find('.rc-tree-checkbox').first().simulate('click');
+      let node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
+      wrapper
+        .find('.rc-tree-checkbox')
+        .first()
+        .simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         ['0-0', '0-0-0'],
         objectMatcher({
@@ -336,8 +393,16 @@ describe('Tree Basic', () => {
         }),
       );
 
-      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).last().props());
-      wrapper.find('.rc-tree-checkbox').last().simulate('click');
+      node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .last()
+          .props(),
+      );
+      wrapper
+        .find('.rc-tree-checkbox')
+        .last()
+        .simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         [],
         objectMatcher({
@@ -362,7 +427,10 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
+      wrapper
+        .find('.rc-tree-checkbox')
+        .at(1)
+        .simulate('click');
       wrapper.setProps({
         children: (
           <TreeNode title="parent 1" key="0-0">
@@ -372,7 +440,12 @@ describe('Tree Basic', () => {
           </TreeNode>
         ),
       });
-      expect(() => wrapper.find('.rc-tree-checkbox').at(2).simulate('click')).not.toThrow();
+      expect(() =>
+        wrapper
+          .find('.rc-tree-checkbox')
+          .at(2)
+          .simulate('click'),
+      ).not.toThrow();
     });
     // https://github.com/react-component/tree/issues/90
     it('check works correctly after adding children dynamically', () => {
@@ -383,7 +456,10 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
+      wrapper
+        .find('.rc-tree-checkbox')
+        .at(1)
+        .simulate('click');
       wrapper.setProps({
         children: (
           <TreeNode title="parent 1" key="0-0">
@@ -392,13 +468,18 @@ describe('Tree Basic', () => {
           </TreeNode>
         ),
       });
-      expect(() => wrapper.find('.rc-tree-checkbox').at(2).simulate('click')).not.toThrow();
+      expect(() =>
+        wrapper
+          .find('.rc-tree-checkbox')
+          .at(2)
+          .simulate('click'),
+      ).not.toThrow();
     });
 
     // https://github.com/ant-design/ant-design/issues/7353
     it('check children after changing from children[disableCheckbox] from true to false', () => {
       let checkedKeys = null;
-      const mockHandleCheck = (keys) => (checkedKeys = keys);
+      const mockHandleCheck = keys => (checkedKeys = keys);
       function Test({ disableCheckbox }) {
         return (
           <Tree checkable onCheck={mockHandleCheck}>
@@ -411,10 +492,17 @@ describe('Tree Basic', () => {
         );
       }
       const wrapper = mount(<Test disableCheckbox />);
-      wrapper.find('.rc-tree-checkbox').first().simulate('click');
+      wrapper
+        .find('.rc-tree-checkbox')
+        .first()
+        .simulate('click');
       expect(checkedKeys).toEqual(['0-0']);
       wrapper.setProps({ disableCheckbox: false });
-      wrapper.find('.rc-tree-checkbox').first().simulate('click').simulate('click');
+      wrapper
+        .find('.rc-tree-checkbox')
+        .first()
+        .simulate('click')
+        .simulate('click');
       expect(checkedKeys).toEqual(['0-0', '0-0-1', '0-0-2', '0-0-3']);
     });
 
@@ -454,7 +542,10 @@ describe('Tree Basic', () => {
         }
       }
       const wrapper = mount(<Test />);
-      wrapper.find('.rc-tree-checkbox').first().simulate('click');
+      wrapper
+        .find('.rc-tree-checkbox')
+        .first()
+        .simulate('click');
 
       expect(renderToJson(wrapper.render())).toMatchSnapshot();
     });
@@ -491,7 +582,10 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      wrapper.find('.rc-tree-checkbox').last().simulate('click');
+      wrapper
+        .find('.rc-tree-checkbox')
+        .last()
+        .simulate('click');
 
       expect(onCheck.mock.calls[0][0].sort()).toEqual(['0-0', '0-0-1']);
     });
@@ -506,9 +600,22 @@ describe('Tree Basic', () => {
           </Tree>,
         );
         wrapper.find('.rc-tree-switcher').simulate('click');
-        wrapper.find('.rc-tree-checkbox').first().simulate('click');
-        expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
-        expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
+        wrapper
+          .find('.rc-tree-checkbox')
+          .first()
+          .simulate('click');
+        expect(
+          wrapper
+            .find('.rc-tree-checkbox')
+            .first()
+            .is(CHECKED_CLASSNAME),
+        ).toBe(true);
+        expect(
+          wrapper
+            .find('.rc-tree-checkbox')
+            .last()
+            .is(CHECKED_CLASSNAME),
+        ).toBe(false);
       });
 
       describe('controlled mode', () => {
@@ -520,7 +627,7 @@ describe('Tree Basic', () => {
             },
           };
 
-          handleCheck = (checkedKeys) => {
+          handleCheck = checkedKeys => {
             this.setState({ checkedKeys });
           };
 
@@ -543,9 +650,22 @@ describe('Tree Basic', () => {
         it('do not check children', () => {
           const wrapper = mount(<App />);
           wrapper.find('.rc-tree-switcher').simulate('click');
-          wrapper.find('.rc-tree-checkbox').first().simulate('click');
-          expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
-          expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
+          wrapper
+            .find('.rc-tree-checkbox')
+            .first()
+            .simulate('click');
+          expect(
+            wrapper
+              .find('.rc-tree-checkbox')
+              .first()
+              .is(CHECKED_CLASSNAME),
+          ).toBe(true);
+          expect(
+            wrapper
+              .find('.rc-tree-checkbox')
+              .last()
+              .is(CHECKED_CLASSNAME),
+          ).toBe(false);
         });
 
         it('do not uncheck parent', () => {
@@ -554,9 +674,22 @@ describe('Tree Basic', () => {
             checkedKeys: { checked: ['0-0', '0-0-0'], halfChecked: [] },
           });
           wrapper.find('.rc-tree-switcher').simulate('click');
-          wrapper.find('.rc-tree-checkbox').last().simulate('click');
-          expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
-          expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
+          wrapper
+            .find('.rc-tree-checkbox')
+            .last()
+            .simulate('click');
+          expect(
+            wrapper
+              .find('.rc-tree-checkbox')
+              .first()
+              .is(CHECKED_CLASSNAME),
+          ).toBe(true);
+          expect(
+            wrapper
+              .find('.rc-tree-checkbox')
+              .last()
+              .is(CHECKED_CLASSNAME),
+          ).toBe(false);
         });
       });
     });
@@ -581,7 +714,12 @@ describe('Tree Basic', () => {
 
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
       expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(wrapper.state().checkedKeys.slice().sort()).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
+      expect(
+        wrapper
+          .state()
+          .checkedKeys.slice()
+          .sort(),
+      ).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
@@ -603,12 +741,22 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      const firstNode = () => wrapper.find(TreeNode).first().find('.rc-tree-checkbox').first();
+      const firstNode = () =>
+        wrapper
+          .find(TreeNode)
+          .first()
+          .find('.rc-tree-checkbox')
+          .first();
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
       expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(wrapper.state().checkedKeys.slice().sort()).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
+      expect(
+        wrapper
+          .state()
+          .checkedKeys.slice()
+          .sort(),
+      ).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
@@ -630,8 +778,18 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      const getParent = () => wrapper.find(TreeNode).at(1).find('.rc-tree-checkbox').first();
-      const getNode = () => wrapper.find(TreeNode).at(2).find('.rc-tree-checkbox').first();
+      const getParent = () =>
+        wrapper
+          .find(TreeNode)
+          .at(1)
+          .find('.rc-tree-checkbox')
+          .first();
+      const getNode = () =>
+        wrapper
+          .find(TreeNode)
+          .at(2)
+          .find('.rc-tree-checkbox')
+          .first();
 
       getNode().simulate('click');
       expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
@@ -680,7 +838,12 @@ describe('Tree Basic', () => {
       );
       const nodeContent = wrapper.find('.rc-tree-node-content-wrapper');
 
-      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
+      let node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       nodeContent.simulate('click');
       expect(handleSelect).toHaveBeenCalledWith(
         ['0-0'],
@@ -693,7 +856,12 @@ describe('Tree Basic', () => {
         }),
       );
 
-      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
+      node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
       nodeContent.simulate('click');
       expect(handleSelect).toHaveBeenCalledWith(
         [],
@@ -723,8 +891,16 @@ describe('Tree Basic', () => {
       const dataNode1 = { title: 'parent 1', key: '0-0' };
       const dataNode2 = { title: 'leaf 1', key: '0-0-0' };
 
-      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
-      wrapper.find('.rc-tree-node-content-wrapper').first().simulate('click');
+      let node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .first()
+          .props(),
+      );
+      wrapper
+        .find('.rc-tree-node-content-wrapper')
+        .first()
+        .simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         ['0-0', '0-0-0'],
         objectMatcher({
@@ -741,8 +917,16 @@ describe('Tree Basic', () => {
         }),
       );
 
-      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).last().props());
-      wrapper.find('.rc-tree-node-content-wrapper').last().simulate('click');
+      node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .last()
+          .props(),
+      );
+      wrapper
+        .find('.rc-tree-node-content-wrapper')
+        .last()
+        .simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         [],
         objectMatcher({
@@ -829,7 +1013,12 @@ describe('Tree Basic', () => {
       </Tree>,
     );
 
-    expect(wrapper.find(InternalTreeNode).find('div').is('.filter-node')).toBe(true);
+    expect(
+      wrapper
+        .find(InternalTreeNode)
+        .find('div')
+        .is('.filter-node'),
+    ).toBe(true);
   });
 
   it('loads nodes asynchronously', () => {
@@ -862,7 +1051,12 @@ describe('Tree Basic', () => {
       const onDragStart = jest.fn();
       const wrapper = mount(createTree({ onDragStart }));
       const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
+      const node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(1)
+          .props(),
+      );
       treeNode.simulate('dragStart');
       const event = onDragStart.mock.calls[0][0];
       expect(event.node).toEqual(node);
@@ -879,12 +1073,20 @@ describe('Tree Basic', () => {
       await delay(900);
       expect(onDragEnter).not.toHaveBeenCalled();
 
-      wrapper.find('.dropTarget').at(0).simulate('dragEnter');
+      wrapper
+        .find('.dropTarget')
+        .at(0)
+        .simulate('dragEnter');
       expect(onDragEnter).toHaveBeenCalled();
 
       await delay(900);
       wrapper.update();
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+      const node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(2)
+          .props(),
+      );
       const event = onDragEnter.mock.calls[0][0];
       expect(event.node.key).toEqual(node.key);
       expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
@@ -894,9 +1096,17 @@ describe('Tree Basic', () => {
     it('fires dragOver event', () => {
       const onDragOver = jest.fn();
       const wrapper = mount(createTree({ onDragOver }));
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+      const node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(2)
+          .props(),
+      );
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      wrapper.find('.dropTarget').at(0).simulate('dragOver');
+      wrapper
+        .find('.dropTarget')
+        .at(0)
+        .simulate('dragOver');
       const event = onDragOver.mock.calls[0][0];
       expect(event.node).toEqual(node);
     });
@@ -904,8 +1114,16 @@ describe('Tree Basic', () => {
     it('fires dragLeave event', () => {
       const onDragLeave = jest.fn();
       const wrapper = mount(createTree({ onDragLeave }));
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-      wrapper.find('.dropTarget').at(0).simulate('dragLeave');
+      const node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(2)
+          .props(),
+      );
+      wrapper
+        .find('.dropTarget')
+        .at(0)
+        .simulate('dragLeave');
       const event = onDragLeave.mock.calls[0][0];
       expect(event.node).toEqual(node);
     });
@@ -916,9 +1134,22 @@ describe('Tree Basic', () => {
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
       wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
       wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
-      const dropNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-      const dragNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
-      wrapper.find('.dropTarget').at(0).simulate('drop');
+      const dropNode = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(2)
+          .props(),
+      );
+      const dragNode = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(1)
+          .props(),
+      );
+      wrapper
+        .find('.dropTarget')
+        .at(0)
+        .simulate('drop');
       const event = onDrop.mock.calls[0][0];
       expect(event.node).toEqual(dropNode);
       expect(event.dragNode).toEqual(dragNode);
@@ -928,7 +1159,12 @@ describe('Tree Basic', () => {
     it('fires dropEnd event', () => {
       const onDragEnd = jest.fn();
       const wrapper = mount(createTree({ onDragEnd }));
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
+      const node = convertNodePropsToEventData(
+        wrapper
+          .find(InternalTreeNode)
+          .at(1)
+          .props(),
+      );
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
       const event = onDragEnd.mock.calls[0][0];
       expect(event.node).toEqual(node);
@@ -946,14 +1182,23 @@ describe('Tree Basic', () => {
       const normalTree = wrapper.find(Tree).at(1);
 
       dragTree.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      normalTree.find('.dropTarget').at(0).simulate('dragEnter');
-      normalTree.find('.dropTarget').at(0).simulate('dragOver');
-      normalTree.find('.dropTarget').at(0).simulate('drop');
+      normalTree
+        .find('.dropTarget')
+        .at(0)
+        .simulate('dragEnter');
+      normalTree
+        .find('.dropTarget')
+        .at(0)
+        .simulate('dragOver');
+      normalTree
+        .find('.dropTarget')
+        .at(0)
+        .simulate('drop');
     });
 
     describe('full steps', () => {
       function dropTarget(targetSelector) {
-        return new Promise((resolve) => {
+        return new Promise(resolve => {
           const wrapper = mount(
             <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
               <TreeNode key="0-0" className="dragTarget">
@@ -1050,7 +1295,7 @@ describe('Tree Basic', () => {
       afterEach(() => {
         domSpy.mockRestore();
       });
-      ['ltr', 'rtl'].forEach((dir) => {
+      ['ltr', 'rtl'].forEach(dir => {
         const base = dir === 'ltr' ? 1 : -1;
         it('not allow cross level dnd on expanded nodes', () => {
           const onDrop = jest.fn();
@@ -1521,9 +1766,9 @@ describe('Tree Basic', () => {
     expect(() => {
       mount(
         <Tree>
-          {[0, 1].map((i) => (
+          {[0, 1].map(i => (
             <TreeNode title={i} key={i}>
-              {[2, 3].map((j) => (
+              {[2, 3].map(j => (
                 <TreeNode title={j} key={`${i}_${j}`} />
               ))}
               <TreeNode title="4" key={`${i}_4`} />
@@ -1538,7 +1783,7 @@ describe('Tree Basic', () => {
     const wrapper = render(
       <Tree>
         <TreeNode title="0" key="0">
-          {[1, 2].map((i) => (
+          {[1, 2].map(i => (
             <TreeNode title={i} key={i} />
           ))}
           <TreeNode title="3" key="3" />
@@ -1596,7 +1841,7 @@ describe('Tree Basic', () => {
           checkable
           defaultExpandAll
           checkedKeys={checkedKeys}
-          onCheck={(keys) => {
+          onCheck={keys => {
             setCheckedKeys(keys);
             onCheck(keys);
           }}
@@ -1608,11 +1853,17 @@ describe('Tree Basic', () => {
     const wrapper = mount(<Demo />);
     wrapper.update();
 
-    wrapper.find('.rc-tree-checkbox').first().simulate('click');
+    wrapper
+      .find('.rc-tree-checkbox')
+      .first()
+      .simulate('click');
     expect(onCheck).toHaveBeenCalledWith([11, 22]);
 
     onCheck.mockReset();
-    wrapper.find('.rc-tree-checkbox').last().simulate('click');
+    wrapper
+      .find('.rc-tree-checkbox')
+      .last()
+      .simulate('click');
     expect(onCheck).toHaveBeenCalledWith([]);
   });
 
@@ -1672,7 +1923,10 @@ describe('Tree Basic', () => {
     wrapper.setProps({ expandedKeys: undefined });
 
     // Click should not crash
-    wrapper.find('.rc-tree-switcher').first().simulate('click');
+    wrapper
+      .find('.rc-tree-switcher')
+      .first()
+      .simulate('click');
   });
 
   it('controlled should block expanded', () => {
@@ -1683,7 +1937,10 @@ describe('Tree Basic', () => {
       />,
     );
 
-    wrapper.find('.rc-tree-switcher').first().simulate('click');
+    wrapper
+      .find('.rc-tree-switcher')
+      .first()
+      .simulate('click');
     expect(wrapper.find('List').props().data).toHaveLength(2);
   });
 
@@ -1714,13 +1971,23 @@ describe('Tree Basic', () => {
 
     // trigger click to expand node
     wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(wrapper.find('.rc-tree-switcher').first().is(OPEN_CLASSNAME)).toBe(true);
+    expect(
+      wrapper
+        .find('.rc-tree-switcher')
+        .first()
+        .is(OPEN_CLASSNAME),
+    ).toBe(true);
     expect(onExpand).toBeCalled();
 
     // click again
     onExpand.mockReset();
     wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(wrapper.find('.rc-tree-switcher').first().is(OPEN_CLASSNAME)).toBe(true);
+    expect(
+      wrapper
+        .find('.rc-tree-switcher')
+        .first()
+        .is(OPEN_CLASSNAME),
+    ).toBe(true);
     expect(onExpand).not.toHaveBeenCalled();
   });
 });

From a003237bb774dd0387450271409f29427654018f Mon Sep 17 00:00:00 2001
From: chenshuai2144 <qixian.cs@outlook.com>
Date: Wed, 10 Mar 2021 12:19:35 +0800
Subject: [PATCH 178/397] v4.1.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b2d5c570..09a3b5a1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.1.4",
+  "version": "4.1.5",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=8.x"

From b25f6867869ad586a85cdff82821f09bedea3bea Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Fri, 25 Jun 2021 17:57:50 +0800
Subject: [PATCH 179/397] fix start warning

---
 src/index.ts | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/src/index.ts b/src/index.ts
index d5cb7cd9..6df17209 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,8 @@
-import Tree, { TreeProps } from './Tree';
-import TreeNode, { TreeNodeProps } from './TreeNode';
+import Tree from './Tree';
+import TreeNode from './TreeNode';
+import type { TreeProps } from './Tree';
+import type { TreeNodeProps } from './TreeNode';
 
-export { TreeNode, TreeProps, TreeNodeProps };
+export { TreeNode };
+export type { TreeProps, TreeNodeProps };
 export default Tree;

From 2f6a514d18061855123a8583be15a1435710768e Mon Sep 17 00:00:00 2001
From: Jehu <z_zhihao@foxmail.com>
Date: Mon, 28 Jun 2021 11:01:38 +0800
Subject: [PATCH 180/397] fix: update role pos (#460)

---
 src/NodeList.tsx                              |   4 +-
 src/Tree.tsx                                  |   1 +
 tests/__snapshots__/Tree.spec.js.snap         |  35 ++---
 .../__snapshots__/TreeNodeProps.spec.js.snap  |  45 +++----
 tests/__snapshots__/TreeProps.spec.js.snap    | 125 +++++++-----------
 5 files changed, 85 insertions(+), 125 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index ca4a014b..90025c66 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -170,7 +170,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
   const listRef = React.useRef<ListRef>(null);
   const indentMeasurerRef = React.useRef<HTMLDivElement>(null);
   React.useImperativeHandle(ref, () => ({
-    scrollTo: scroll => {
+    scrollTo: (scroll) => {
       listRef.current.scrollTo(scroll);
     },
     getIndentWidth: () => indentMeasurerRef.current.offsetWidth,
@@ -268,7 +268,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         </span>
       )}
 
-      <div role="tree">
+      <div>
         <input
           style={HIDDEN_STYLE}
           disabled={focusable === false || disabled}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 4e691017..746f338d 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -1310,6 +1310,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         }}
       >
         <div
+          role="tree"
           className={classNames(prefixCls, className, {
             [`${prefixCls}-show-line`]: showLine,
             [`${prefixCls}-focused`]: focused,
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index bf3589f7..c6ed3586 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -3,10 +3,9 @@
 exports[`Tree Basic check basic render 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -137,10 +136,9 @@ exports[`Tree Basic check basic render 1`] = `
 exports[`Tree Basic check check after data ready works 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -240,10 +238,9 @@ exports[`Tree Basic check check after data ready works 1`] = `
 exports[`Tree Basic check check update when Tree trigger componentWillReceiveProps 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -312,10 +309,9 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
 exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -405,10 +401,9 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
 exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -554,10 +549,9 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
 exports[`Tree Basic renders correctly 1`] = `
 <div
   class="rc-tree forTest rc-tree-show-line"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -756,10 +750,9 @@ exports[`Tree Basic renders correctly 1`] = `
 exports[`Tree Basic renders opaque children correctly 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index ec30cdc8..72c885df 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -3,10 +3,9 @@
 exports[`TreeNode Props className 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -183,10 +182,9 @@ exports[`TreeNode Props className 1`] = `
 exports[`TreeNode Props customize icon component 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -256,10 +254,9 @@ exports[`TreeNode Props customize icon component 1`] = `
 exports[`TreeNode Props customize icon element 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -329,10 +326,9 @@ exports[`TreeNode Props customize icon element 1`] = `
 exports[`TreeNode Props customize icon hide icon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -395,10 +391,9 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
 exports[`TreeNode Props data and aria props renders aria attributes on li 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -580,10 +575,9 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
 exports[`TreeNode Props data and aria props renders data attributes on li 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -765,10 +759,9 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
 exports[`TreeNode Props isLeaf 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -856,10 +849,9 @@ exports[`TreeNode Props isLeaf 1`] = `
 exports[`TreeNode Props isLeaf 2`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -953,10 +945,9 @@ exports[`TreeNode Props isLeaf 2`] = `
 exports[`TreeNode Props isLeaf 3`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index f889b4c2..80a7552d 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -3,10 +3,9 @@
 exports[`Tree Props checkStrictly 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -106,10 +105,9 @@ exports[`Tree Props checkStrictly 1`] = `
 exports[`Tree Props checkable default 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -209,10 +207,9 @@ exports[`Tree Props checkable default 1`] = `
 exports[`Tree Props checkable without selectable 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -312,10 +309,9 @@ exports[`Tree Props checkable without selectable 1`] = `
 exports[`Tree Props custom switcher icon switcher icon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -465,10 +461,9 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
 exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -682,10 +677,9 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
 exports[`Tree Props data and aria props renders aria attributes 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -727,10 +721,9 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
 exports[`Tree Props data and aria props renders data attributes 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -772,10 +765,9 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
 exports[`Tree Props defaultExpandAll 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -869,10 +861,9 @@ exports[`Tree Props defaultExpandAll 1`] = `
 exports[`Tree Props disabled basic 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
@@ -939,10 +930,9 @@ exports[`Tree Props disabled basic 1`] = `
 exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
@@ -1009,10 +999,9 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
 exports[`Tree Props icon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1114,10 +1103,9 @@ exports[`Tree Props icon 1`] = `
 exports[`Tree Props invalidate checkedKeys null 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -1217,10 +1205,9 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
 exports[`Tree Props invalidate checkedKeys number 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -1320,10 +1307,9 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
 exports[`Tree Props loadData basic 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -1411,10 +1397,9 @@ exports[`Tree Props loadData basic 1`] = `
 exports[`Tree Props multiple 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1508,10 +1493,9 @@ exports[`Tree Props multiple 1`] = `
 exports[`Tree Props prefixCls 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1552,10 +1536,9 @@ exports[`Tree Props prefixCls 1`] = `
 exports[`Tree Props prefixCls 2`] = `
 <div
   class="test-prefix"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1596,10 +1579,9 @@ exports[`Tree Props prefixCls 2`] = `
 exports[`Tree Props selectable with selectable 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1693,10 +1675,9 @@ exports[`Tree Props selectable with selectable 1`] = `
 exports[`Tree Props selectable without selectable 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1790,10 +1771,9 @@ exports[`Tree Props selectable without selectable 1`] = `
 exports[`Tree Props showIcon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1883,10 +1863,9 @@ exports[`Tree Props showIcon 1`] = `
 exports[`Tree Props showIcon 2`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1970,10 +1949,9 @@ exports[`Tree Props showIcon 2`] = `
 exports[`Tree Props showIcon 3`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -2154,10 +2132,9 @@ exports[`Tree Props showIcon 3`] = `
 exports[`Tree Props showLine 1`] = `
 <div
   class="rc-tree rc-tree-show-line"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -2198,10 +2175,9 @@ exports[`Tree Props showLine 1`] = `
 exports[`Tree Props treeData 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -2437,10 +2413,9 @@ exports[`Tree Props treeData 1`] = `
 exports[`Tree Props treeData 2`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"

From fab481fbd186907da0f8759843ec16da8e64f064 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 28 Jun 2021 11:02:01 +0800
Subject: [PATCH 181/397] chore(deps-dev): bump @types/react-dom from 16.9.13
 to 17.0.8 (#466)

Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 16.9.13 to 17.0.8.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 09a3b5a1..ad1b66be 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
   "devDependencies": {
     "@types/jest": "^26.0.4",
     "@types/react": "^16.8.19",
-    "@types/react-dom": "^16.8.4",
+    "@types/react-dom": "^17.0.8",
     "@types/warning": "^3.0.0",
     "@umijs/fabric": "^2.4.8",
     "css-animation": "^1.2.0",

From 5d8fe5ccfa955a54008719cf12fd52d67592be6b Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 28 Jun 2021 11:04:37 +0800
Subject: [PATCH 182/397] Update README.md

---
 README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 76b20514..1616a90c 100644
--- a/README.md
+++ b/README.md
@@ -2,14 +2,14 @@
 
 Tree component.
 
-[![NPM version][npm-image]][npm-url] [![dumi](https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square)](https://github.com/umijs/dumi) [![build status][github-actions-image]][github-actions-url] [![Test coverage][coveralls-image]][coveralls-url] [![Dependencies][david-image]][david-url] [![DevDependencies][david-dev-image]][david-dev-url] [![npm download][download-image]][download-url] [![bundle size][bundlephobia-image]][bundlephobia-url]
+[![NPM version][npm-image]][npm-url] [![dumi](https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square)](https://github.com/umijs/dumi) [![build status][github-actions-image]][github-actions-url] [![Test coverage][codecov-image]][codecov-url] [![Dependencies][david-image]][david-url] [![DevDependencies][david-dev-image]][david-dev-url] [![npm download][download-image]][download-url] [![bundle size][bundlephobia-image]][bundlephobia-url]
 
 [npm-image]: http://img.shields.io/npm/v/rc-tree.svg?style=flat-square
 [npm-url]: http://npmjs.org/package/rc-tree
 [github-actions-image]: https://github.com/react-component/tree/workflows/CI/badge.svg
 [github-actions-url]: https://github.com/react-component/tree/actions
-[coveralls-image]: https://img.shields.io/coveralls/react-component/tree.svg?style=flat-square
-[coveralls-url]: https://coveralls.io/r/react-component/tree?branch=master
+[codecov-image]: https://img.shields.io/codecov/c/github/react-component/tree/master.svg?style=flat-square
+[codecov-url]: https://codecov.io/gh/react-component/tree/branch/master
 [david-url]: https://david-dm.org/react-component/tree
 [david-image]: https://david-dm.org/react-component/tree/status.svg?style=flat-square
 [david-dev-url]: https://david-dm.org/react-component/tree?type=dev

From 8136db204d8751f66a6bacaed077a04274218b38 Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 28 Jun 2021 11:21:02 +0800
Subject: [PATCH 183/397] chore(deps-dev): bump @types/react from 16.14.8 to
 17.0.11 (#463)

Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 16.14.8 to 17.0.11.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index ad1b66be..c88709b9 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
   },
   "devDependencies": {
     "@types/jest": "^26.0.4",
-    "@types/react": "^16.8.19",
+    "@types/react": "^17.0.11",
     "@types/react-dom": "^17.0.8",
     "@types/warning": "^3.0.0",
     "@umijs/fabric": "^2.4.8",

From ff0522f06451097f6fe8db5291ebf50ac13543a5 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 28 Jun 2021 11:21:46 +0800
Subject: [PATCH 184/397] Update package.json

---
 package.json | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/package.json b/package.json
index c88709b9..f3946079 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
   "version": "4.1.5",
   "description": "tree ui component for react",
   "engines": {
-    "node": ">=8.x"
+    "node": ">=12.x"
   },
   "keywords": [
     "react",
@@ -55,7 +55,6 @@
     "@types/react-dom": "^17.0.8",
     "@types/warning": "^3.0.0",
     "@umijs/fabric": "^2.4.8",
-    "css-animation": "^1.2.0",
     "dumi": "^1.1.4",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",

From 3674c801dedfafbb6311e5a031ac701637ef146d Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 28 Jun 2021 18:25:37 +0800
Subject: [PATCH 185/397] Upgrade to GitHub-native Dependabot (#449)

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 .github/dependabot.yml | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 .github/dependabot.yml

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..afd4cd08
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,31 @@
+version: 2
+updates:
+- package-ecosystem: npm
+  directory: "/"
+  schedule:
+    interval: daily
+    time: "21:00"
+  open-pull-requests-limit: 10
+  ignore:
+  - dependency-name: "@types/react"
+    versions:
+    - 17.0.0
+    - 17.0.1
+    - 17.0.2
+    - 17.0.3
+  - dependency-name: "@types/react-dom"
+    versions:
+    - 17.0.0
+    - 17.0.1
+    - 17.0.2
+  - dependency-name: np
+    versions:
+    - 7.2.0
+    - 7.3.0
+    - 7.4.0
+  - dependency-name: rc-tooltip
+    versions:
+    - 5.0.2
+  - dependency-name: less
+    versions:
+    - 4.1.0

From 24c007c0b02d5facba271ea59fb54b3a1b51c1bf Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 28 Jun 2021 18:34:58 +0800
Subject: [PATCH 186/397] fix: Tree state when loadData fail (#469)

* fix: handle loadData fail situation

close https://github.com/ant-design/ant-design/issues/31141

* restore expanded tree node after loadData fail

* remove react 15 comment

* throw error after handle

* fix lint

* fix comment

* fix Useless conditional

* fix code style

* add test case

* fix test case

* remove console.log
---
 src/Tree.tsx            |  79 ++++----
 src/TreeNode.tsx        |   4 +-
 tests/Tree.spec.js      | 420 +++++++++-------------------------------
 tests/TreeProps.spec.js | 108 +++--------
 4 files changed, 170 insertions(+), 441 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 746f338d..b1cb1d85 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -659,13 +659,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeDrop = (event: React.MouseEvent<HTMLDivElement>, node, outsideTree: boolean = false) => {
-    const {
-      dragChildrenKeys,
-      dropPosition,
-      dropTargetKey,
-      dropTargetPos,
-      dropAllowed,
-    } = this.state;
+    const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } =
+      this.state;
 
     if (!dropAllowed) return;
 
@@ -863,43 +858,51 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeLoad = (treeNode: EventDataNode) =>
-    new Promise<void>((resolve) => {
+    new Promise<void>((resolve, reject) => {
       // We need to get the latest state of loading/loaded keys
       this.setState(({ loadedKeys = [], loadingKeys = [] }): any => {
         const { loadData, onLoad } = this.props;
         const { key } = treeNode;
 
         if (!loadData || loadedKeys.indexOf(key) !== -1 || loadingKeys.indexOf(key) !== -1) {
-          // react 15 will warn if return null
-          return {};
+          return null;
         }
 
         // Process load data
         const promise = loadData(treeNode);
-        promise.then(() => {
-          const { loadedKeys: currentLoadedKeys, loadingKeys: currentLoadingKeys } = this.state;
-          const newLoadedKeys = arrAdd(currentLoadedKeys, key);
-          const newLoadingKeys = arrDel(currentLoadingKeys, key);
-
-          // onLoad should trigger before internal setState to avoid `loadData` trigger twice.
-          // https://github.com/ant-design/ant-design/issues/12464
-          if (onLoad) {
-            onLoad(newLoadedKeys, {
-              event: 'load',
-              node: treeNode,
+        promise
+          .then(() => {
+            const { loadedKeys: currentLoadedKeys, loadingKeys: currentLoadingKeys } = this.state;
+            const newLoadedKeys = arrAdd(currentLoadedKeys, key);
+            const newLoadingKeys = arrDel(currentLoadingKeys, key);
+
+            // onLoad should trigger before internal setState to avoid `loadData` trigger twice.
+            // https://github.com/ant-design/ant-design/issues/12464
+            if (onLoad) {
+              onLoad(newLoadedKeys, {
+                event: 'load',
+                node: treeNode,
+              });
+            }
+
+            this.setUncontrolledState({
+              loadedKeys: newLoadedKeys,
+            });
+            this.setState({
+              loadingKeys: newLoadingKeys,
             });
-          }
 
-          this.setUncontrolledState({
-            loadedKeys: newLoadedKeys,
-          });
-          this.setState({
-            loadingKeys: newLoadingKeys,
+            resolve();
+          })
+          .catch((e) => {
+            const { loadingKeys: currentLoadingKeys } = this.state;
+            const newLoadingKeys = arrDel(currentLoadingKeys, key);
+            this.setState({
+              loadingKeys: newLoadingKeys,
+            });
+            reject(e);
           });
 
-          resolve();
-        });
-
         return {
           loadingKeys: arrAdd(loadingKeys, key),
         };
@@ -1027,11 +1030,17 @@ class Tree extends React.Component<TreeProps, TreeState> {
     if (targetExpanded && loadData) {
       const loadPromise = this.onNodeLoad(treeNode);
       if (loadPromise) {
-        loadPromise.then(() => {
-          // [Legacy] Refresh logic
-          const newFlattenTreeData = flattenTreeData(this.state.treeData, expandedKeys);
-          this.setUncontrolledState({ flattenNodes: newFlattenTreeData });
-        });
+        loadPromise
+          .then(() => {
+            // [Legacy] Refresh logic
+            const newFlattenTreeData = flattenTreeData(this.state.treeData, expandedKeys);
+            this.setUncontrolledState({ flattenNodes: newFlattenTreeData });
+          })
+          .catch(() => {
+            const { expandedKeys: currentExpandedKeys } = this.state;
+            const expandedKeysToRestore = arrDel(currentExpandedKeys, key);
+            this.setExpandedKeys(expandedKeysToRestore);
+          });
       }
     }
   };
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 9df145a5..98485b0b 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -291,7 +291,9 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       context: { loadData, onNodeLoad },
     } = this.props;
 
-    if (loading) return;
+    if (loading) {
+      return;
+    };
 
     // read from state to avoid loadData at same time
     if (loadData && expanded && !this.isLeaf()) {
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 93845f55..308b737c 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -15,8 +15,8 @@ const OPEN_CLASSNAME = '.rc-tree-switcher_open';
 const CHECKED_CLASSNAME = '.rc-tree-checkbox-checked';
 const SELECTED_CLASSNAME = '.rc-tree-node-selected';
 
-const delay = timeout =>
-  new Promise(resolve => {
+const delay = (timeout) =>
+  new Promise((resolve) => {
     setTimeout(resolve, timeout);
   });
 
@@ -197,12 +197,7 @@ describe('Tree Basic', () => {
       );
       const switcher = wrapper.find('.rc-tree-switcher');
 
-      let node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
+      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
       switcher.simulate('click');
       expect(handleExpand).toHaveBeenCalledWith(['0-0'], {
         expanded: true,
@@ -210,12 +205,7 @@ describe('Tree Basic', () => {
         nativeEvent: expect.anything(),
       });
 
-      node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
+      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
       switcher.simulate('click');
       expect(handleExpand).toHaveBeenCalledWith([], {
         expanded: false,
@@ -248,7 +238,7 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
+      wrapper.find('.rc-tree-checkbox').forEach((checkbox) => {
         expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
       });
     });
@@ -292,20 +282,9 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .at(1)
-        .simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .at(2)
-        .simulate('click');
-      expect(
-        wrapper
-          .find('.rc-tree-checkbox')
-          .first()
-          .is(CHECKED_CLASSNAME),
-      ).toBe(true);
+      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
+      wrapper.find('.rc-tree-checkbox').at(2).simulate('click');
+      expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
     });
 
     it('turns parent node to half checked when child is checked', () => {
@@ -318,16 +297,10 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .last()
-        .simulate('click');
-      expect(
-        wrapper
-          .find('.rc-tree-checkbox')
-          .first()
-          .is('.rc-tree-checkbox-indeterminate'),
-      ).toBe(true);
+      wrapper.find('.rc-tree-checkbox').last().simulate('click');
+      expect(wrapper.find('.rc-tree-checkbox').first().is('.rc-tree-checkbox-indeterminate')).toBe(
+        true,
+      );
     });
 
     it('turns parent node to checked if it is half checked', () => {
@@ -340,15 +313,9 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .last()
-        .simulate('click');
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
+      wrapper.find('.rc-tree-checkbox').last().simulate('click');
+      wrapper.find('.rc-tree-checkbox').first().simulate('click');
+      wrapper.find('.rc-tree-checkbox').forEach((checkbox) => {
         expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
       });
     });
@@ -367,16 +334,8 @@ describe('Tree Basic', () => {
       const nodeData00 = { title: 'parent 1', key: '0-0' };
       const nodeData000 = { title: 'leaf 1', key: '0-0-0' };
 
-      let node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click');
+      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
+      wrapper.find('.rc-tree-checkbox').first().simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         ['0-0', '0-0-0'],
         objectMatcher({
@@ -393,16 +352,8 @@ describe('Tree Basic', () => {
         }),
       );
 
-      node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .last()
-          .props(),
-      );
-      wrapper
-        .find('.rc-tree-checkbox')
-        .last()
-        .simulate('click');
+      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).last().props());
+      wrapper.find('.rc-tree-checkbox').last().simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         [],
         objectMatcher({
@@ -427,10 +378,7 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      wrapper
-        .find('.rc-tree-checkbox')
-        .at(1)
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
       wrapper.setProps({
         children: (
           <TreeNode title="parent 1" key="0-0">
@@ -440,12 +388,7 @@ describe('Tree Basic', () => {
           </TreeNode>
         ),
       });
-      expect(() =>
-        wrapper
-          .find('.rc-tree-checkbox')
-          .at(2)
-          .simulate('click'),
-      ).not.toThrow();
+      expect(() => wrapper.find('.rc-tree-checkbox').at(2).simulate('click')).not.toThrow();
     });
     // https://github.com/react-component/tree/issues/90
     it('check works correctly after adding children dynamically', () => {
@@ -456,10 +399,7 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      wrapper
-        .find('.rc-tree-checkbox')
-        .at(1)
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
       wrapper.setProps({
         children: (
           <TreeNode title="parent 1" key="0-0">
@@ -468,18 +408,13 @@ describe('Tree Basic', () => {
           </TreeNode>
         ),
       });
-      expect(() =>
-        wrapper
-          .find('.rc-tree-checkbox')
-          .at(2)
-          .simulate('click'),
-      ).not.toThrow();
+      expect(() => wrapper.find('.rc-tree-checkbox').at(2).simulate('click')).not.toThrow();
     });
 
     // https://github.com/ant-design/ant-design/issues/7353
     it('check children after changing from children[disableCheckbox] from true to false', () => {
       let checkedKeys = null;
-      const mockHandleCheck = keys => (checkedKeys = keys);
+      const mockHandleCheck = (keys) => (checkedKeys = keys);
       function Test({ disableCheckbox }) {
         return (
           <Tree checkable onCheck={mockHandleCheck}>
@@ -492,17 +427,10 @@ describe('Tree Basic', () => {
         );
       }
       const wrapper = mount(<Test disableCheckbox />);
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').first().simulate('click');
       expect(checkedKeys).toEqual(['0-0']);
       wrapper.setProps({ disableCheckbox: false });
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click')
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').first().simulate('click').simulate('click');
       expect(checkedKeys).toEqual(['0-0', '0-0-1', '0-0-2', '0-0-3']);
     });
 
@@ -542,10 +470,7 @@ describe('Tree Basic', () => {
         }
       }
       const wrapper = mount(<Test />);
-      wrapper
-        .find('.rc-tree-checkbox')
-        .first()
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').first().simulate('click');
 
       expect(renderToJson(wrapper.render())).toMatchSnapshot();
     });
@@ -582,10 +507,7 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      wrapper
-        .find('.rc-tree-checkbox')
-        .last()
-        .simulate('click');
+      wrapper.find('.rc-tree-checkbox').last().simulate('click');
 
       expect(onCheck.mock.calls[0][0].sort()).toEqual(['0-0', '0-0-1']);
     });
@@ -600,22 +522,9 @@ describe('Tree Basic', () => {
           </Tree>,
         );
         wrapper.find('.rc-tree-switcher').simulate('click');
-        wrapper
-          .find('.rc-tree-checkbox')
-          .first()
-          .simulate('click');
-        expect(
-          wrapper
-            .find('.rc-tree-checkbox')
-            .first()
-            .is(CHECKED_CLASSNAME),
-        ).toBe(true);
-        expect(
-          wrapper
-            .find('.rc-tree-checkbox')
-            .last()
-            .is(CHECKED_CLASSNAME),
-        ).toBe(false);
+        wrapper.find('.rc-tree-checkbox').first().simulate('click');
+        expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
+        expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
       });
 
       describe('controlled mode', () => {
@@ -627,7 +536,7 @@ describe('Tree Basic', () => {
             },
           };
 
-          handleCheck = checkedKeys => {
+          handleCheck = (checkedKeys) => {
             this.setState({ checkedKeys });
           };
 
@@ -650,22 +559,9 @@ describe('Tree Basic', () => {
         it('do not check children', () => {
           const wrapper = mount(<App />);
           wrapper.find('.rc-tree-switcher').simulate('click');
-          wrapper
-            .find('.rc-tree-checkbox')
-            .first()
-            .simulate('click');
-          expect(
-            wrapper
-              .find('.rc-tree-checkbox')
-              .first()
-              .is(CHECKED_CLASSNAME),
-          ).toBe(true);
-          expect(
-            wrapper
-              .find('.rc-tree-checkbox')
-              .last()
-              .is(CHECKED_CLASSNAME),
-          ).toBe(false);
+          wrapper.find('.rc-tree-checkbox').first().simulate('click');
+          expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
+          expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
         });
 
         it('do not uncheck parent', () => {
@@ -674,22 +570,9 @@ describe('Tree Basic', () => {
             checkedKeys: { checked: ['0-0', '0-0-0'], halfChecked: [] },
           });
           wrapper.find('.rc-tree-switcher').simulate('click');
-          wrapper
-            .find('.rc-tree-checkbox')
-            .last()
-            .simulate('click');
-          expect(
-            wrapper
-              .find('.rc-tree-checkbox')
-              .first()
-              .is(CHECKED_CLASSNAME),
-          ).toBe(true);
-          expect(
-            wrapper
-              .find('.rc-tree-checkbox')
-              .last()
-              .is(CHECKED_CLASSNAME),
-          ).toBe(false);
+          wrapper.find('.rc-tree-checkbox').last().simulate('click');
+          expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
+          expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
         });
       });
     });
@@ -714,12 +597,7 @@ describe('Tree Basic', () => {
 
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
       expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(
-        wrapper
-          .state()
-          .checkedKeys.slice()
-          .sort(),
-      ).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
+      expect(wrapper.state().checkedKeys.slice().sort()).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
@@ -741,22 +619,12 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      const firstNode = () =>
-        wrapper
-          .find(TreeNode)
-          .first()
-          .find('.rc-tree-checkbox')
-          .first();
+      const firstNode = () => wrapper.find(TreeNode).first().find('.rc-tree-checkbox').first();
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
       expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(
-        wrapper
-          .state()
-          .checkedKeys.slice()
-          .sort(),
-      ).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
+      expect(wrapper.state().checkedKeys.slice().sort()).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
 
       firstNode().simulate('click');
       expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
@@ -778,18 +646,8 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      const getParent = () =>
-        wrapper
-          .find(TreeNode)
-          .at(1)
-          .find('.rc-tree-checkbox')
-          .first();
-      const getNode = () =>
-        wrapper
-          .find(TreeNode)
-          .at(2)
-          .find('.rc-tree-checkbox')
-          .first();
+      const getParent = () => wrapper.find(TreeNode).at(1).find('.rc-tree-checkbox').first();
+      const getNode = () => wrapper.find(TreeNode).at(2).find('.rc-tree-checkbox').first();
 
       getNode().simulate('click');
       expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
@@ -838,12 +696,7 @@ describe('Tree Basic', () => {
       );
       const nodeContent = wrapper.find('.rc-tree-node-content-wrapper');
 
-      let node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
+      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
       nodeContent.simulate('click');
       expect(handleSelect).toHaveBeenCalledWith(
         ['0-0'],
@@ -856,12 +709,7 @@ describe('Tree Basic', () => {
         }),
       );
 
-      node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
+      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
       nodeContent.simulate('click');
       expect(handleSelect).toHaveBeenCalledWith(
         [],
@@ -891,16 +739,8 @@ describe('Tree Basic', () => {
       const dataNode1 = { title: 'parent 1', key: '0-0' };
       const dataNode2 = { title: 'leaf 1', key: '0-0-0' };
 
-      let node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .first()
-          .props(),
-      );
-      wrapper
-        .find('.rc-tree-node-content-wrapper')
-        .first()
-        .simulate('click');
+      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
+      wrapper.find('.rc-tree-node-content-wrapper').first().simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         ['0-0', '0-0-0'],
         objectMatcher({
@@ -917,16 +757,8 @@ describe('Tree Basic', () => {
         }),
       );
 
-      node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .last()
-          .props(),
-      );
-      wrapper
-        .find('.rc-tree-node-content-wrapper')
-        .last()
-        .simulate('click');
+      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).last().props());
+      wrapper.find('.rc-tree-node-content-wrapper').last().simulate('click');
       expect(handleCheck).toHaveBeenCalledWith(
         [],
         objectMatcher({
@@ -1013,12 +845,7 @@ describe('Tree Basic', () => {
       </Tree>,
     );
 
-    expect(
-      wrapper
-        .find(InternalTreeNode)
-        .find('div')
-        .is('.filter-node'),
-    ).toBe(true);
+    expect(wrapper.find(InternalTreeNode).find('div').is('.filter-node')).toBe(true);
   });
 
   it('loads nodes asynchronously', () => {
@@ -1051,12 +878,7 @@ describe('Tree Basic', () => {
       const onDragStart = jest.fn();
       const wrapper = mount(createTree({ onDragStart }));
       const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(1)
-          .props(),
-      );
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
       treeNode.simulate('dragStart');
       const event = onDragStart.mock.calls[0][0];
       expect(event.node).toEqual(node);
@@ -1073,20 +895,12 @@ describe('Tree Basic', () => {
       await delay(900);
       expect(onDragEnter).not.toHaveBeenCalled();
 
-      wrapper
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragEnter');
+      wrapper.find('.dropTarget').at(0).simulate('dragEnter');
       expect(onDragEnter).toHaveBeenCalled();
 
       await delay(900);
       wrapper.update();
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .props(),
-      );
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
       const event = onDragEnter.mock.calls[0][0];
       expect(event.node.key).toEqual(node.key);
       expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
@@ -1096,17 +910,9 @@ describe('Tree Basic', () => {
     it('fires dragOver event', () => {
       const onDragOver = jest.fn();
       const wrapper = mount(createTree({ onDragOver }));
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .props(),
-      );
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      wrapper
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragOver');
+      wrapper.find('.dropTarget').at(0).simulate('dragOver');
       const event = onDragOver.mock.calls[0][0];
       expect(event.node).toEqual(node);
     });
@@ -1114,16 +920,8 @@ describe('Tree Basic', () => {
     it('fires dragLeave event', () => {
       const onDragLeave = jest.fn();
       const wrapper = mount(createTree({ onDragLeave }));
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .props(),
-      );
-      wrapper
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragLeave');
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+      wrapper.find('.dropTarget').at(0).simulate('dragLeave');
       const event = onDragLeave.mock.calls[0][0];
       expect(event.node).toEqual(node);
     });
@@ -1134,22 +932,9 @@ describe('Tree Basic', () => {
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
       wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
       wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
-      const dropNode = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(2)
-          .props(),
-      );
-      const dragNode = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(1)
-          .props(),
-      );
-      wrapper
-        .find('.dropTarget')
-        .at(0)
-        .simulate('drop');
+      const dropNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+      const dragNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
+      wrapper.find('.dropTarget').at(0).simulate('drop');
       const event = onDrop.mock.calls[0][0];
       expect(event.node).toEqual(dropNode);
       expect(event.dragNode).toEqual(dragNode);
@@ -1159,12 +944,7 @@ describe('Tree Basic', () => {
     it('fires dropEnd event', () => {
       const onDragEnd = jest.fn();
       const wrapper = mount(createTree({ onDragEnd }));
-      const node = convertNodePropsToEventData(
-        wrapper
-          .find(InternalTreeNode)
-          .at(1)
-          .props(),
-      );
+      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
       wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
       const event = onDragEnd.mock.calls[0][0];
       expect(event.node).toEqual(node);
@@ -1182,23 +962,14 @@ describe('Tree Basic', () => {
       const normalTree = wrapper.find(Tree).at(1);
 
       dragTree.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      normalTree
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragEnter');
-      normalTree
-        .find('.dropTarget')
-        .at(0)
-        .simulate('dragOver');
-      normalTree
-        .find('.dropTarget')
-        .at(0)
-        .simulate('drop');
+      normalTree.find('.dropTarget').at(0).simulate('dragEnter');
+      normalTree.find('.dropTarget').at(0).simulate('dragOver');
+      normalTree.find('.dropTarget').at(0).simulate('drop');
     });
 
     describe('full steps', () => {
       function dropTarget(targetSelector) {
-        return new Promise(resolve => {
+        return new Promise((resolve) => {
           const wrapper = mount(
             <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
               <TreeNode key="0-0" className="dragTarget">
@@ -1295,7 +1066,7 @@ describe('Tree Basic', () => {
       afterEach(() => {
         domSpy.mockRestore();
       });
-      ['ltr', 'rtl'].forEach(dir => {
+      ['ltr', 'rtl'].forEach((dir) => {
         const base = dir === 'ltr' ? 1 : -1;
         it('not allow cross level dnd on expanded nodes', () => {
           const onDrop = jest.fn();
@@ -1766,9 +1537,9 @@ describe('Tree Basic', () => {
     expect(() => {
       mount(
         <Tree>
-          {[0, 1].map(i => (
+          {[0, 1].map((i) => (
             <TreeNode title={i} key={i}>
-              {[2, 3].map(j => (
+              {[2, 3].map((j) => (
                 <TreeNode title={j} key={`${i}_${j}`} />
               ))}
               <TreeNode title="4" key={`${i}_4`} />
@@ -1783,7 +1554,7 @@ describe('Tree Basic', () => {
     const wrapper = render(
       <Tree>
         <TreeNode title="0" key="0">
-          {[1, 2].map(i => (
+          {[1, 2].map((i) => (
             <TreeNode title={i} key={i} />
           ))}
           <TreeNode title="3" key="3" />
@@ -1841,7 +1612,7 @@ describe('Tree Basic', () => {
           checkable
           defaultExpandAll
           checkedKeys={checkedKeys}
-          onCheck={keys => {
+          onCheck={(keys) => {
             setCheckedKeys(keys);
             onCheck(keys);
           }}
@@ -1853,17 +1624,11 @@ describe('Tree Basic', () => {
     const wrapper = mount(<Demo />);
     wrapper.update();
 
-    wrapper
-      .find('.rc-tree-checkbox')
-      .first()
-      .simulate('click');
+    wrapper.find('.rc-tree-checkbox').first().simulate('click');
     expect(onCheck).toHaveBeenCalledWith([11, 22]);
 
     onCheck.mockReset();
-    wrapper
-      .find('.rc-tree-checkbox')
-      .last()
-      .simulate('click');
+    wrapper.find('.rc-tree-checkbox').last().simulate('click');
     expect(onCheck).toHaveBeenCalledWith([]);
   });
 
@@ -1923,10 +1688,7 @@ describe('Tree Basic', () => {
     wrapper.setProps({ expandedKeys: undefined });
 
     // Click should not crash
-    wrapper
-      .find('.rc-tree-switcher')
-      .first()
-      .simulate('click');
+    wrapper.find('.rc-tree-switcher').first().simulate('click');
   });
 
   it('controlled should block expanded', () => {
@@ -1937,10 +1699,7 @@ describe('Tree Basic', () => {
       />,
     );
 
-    wrapper
-      .find('.rc-tree-switcher')
-      .first()
-      .simulate('click');
+    wrapper.find('.rc-tree-switcher').first().simulate('click');
     expect(wrapper.find('List').props().data).toHaveLength(2);
   });
 
@@ -1971,23 +1730,32 @@ describe('Tree Basic', () => {
 
     // trigger click to expand node
     wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(
-      wrapper
-        .find('.rc-tree-switcher')
-        .first()
-        .is(OPEN_CLASSNAME),
-    ).toBe(true);
+    expect(wrapper.find('.rc-tree-switcher').first().is(OPEN_CLASSNAME)).toBe(true);
     expect(onExpand).toBeCalled();
 
     // click again
     onExpand.mockReset();
     wrapper.find('.rc-tree-switcher').simulate('click');
+    expect(wrapper.find('.rc-tree-switcher').first().is(OPEN_CLASSNAME)).toBe(true);
+    expect(onExpand).not.toHaveBeenCalled();
+  });
+
+  // https://github.com/ant-design/ant-design/issues/31141
+  it('should restore tree node loading and expand status when loadData fail', async () => {
+    const loadData = () => new Promise((_, reject) => setTimeout(reject));
+    const wrapper = mount(
+      <Tree loadData={loadData}>
+        <TreeNode title="parent 1" key="0-0" />
+      </Tree>,
+    );
+    // trigger click to expand node
+    wrapper.find('.rc-tree-switcher').simulate('click');
     expect(
-      wrapper
-        .find('.rc-tree-switcher')
-        .first()
-        .is(OPEN_CLASSNAME),
+      wrapper.find('.rc-tree-switcher').getDOMNode().className.includes('rc-tree-switcher_open'),
+    ).toBe(true);
+    await delay();
+    expect(
+      wrapper.find('.rc-tree-switcher').getDOMNode().className.includes('rc-tree-switcher_close'),
     ).toBe(true);
-    expect(onExpand).not.toHaveBeenCalled();
   });
 });
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index eab85159..6797fa27 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -14,7 +14,7 @@ import { convertNodePropsToEventData } from '../src/utils/treeUtil';
 
 // Promisify timeout to let jest catch works
 function timeoutPromise(delay = 0) {
-  return new Promise(resolve => {
+  return new Promise((resolve) => {
     setTimeout(resolve, delay);
   });
 }
@@ -125,9 +125,7 @@ describe('Tree Props', () => {
 
       // Select leaf
       let node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode()
-        .find('.rc-tree-node-content-wrapper')
-        .simulate('click');
+      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
 
       // traverseTreeNodes loops origin TreeNode and
       // onSelect trigger on `cloneElement` which is not the same instance
@@ -145,9 +143,7 @@ describe('Tree Props', () => {
 
       // un-select leaf
       node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode()
-        .find('.rc-tree-node-content-wrapper')
-        .simulate('click');
+      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
       expect(handleOnSelect).toHaveBeenCalledWith(
         [],
         objectMatcher({
@@ -162,9 +158,7 @@ describe('Tree Props', () => {
 
       // Select leaf and then parent
       node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode()
-        .find('.rc-tree-node-content-wrapper')
-        .simulate('click');
+      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
       expect(handleOnSelect).toHaveBeenCalledWith(
         ['0-0-0'],
         objectMatcher({
@@ -178,10 +172,7 @@ describe('Tree Props', () => {
       handleOnSelect.mockReset();
 
       node = convertNodePropsToEventData(getParentNode().props());
-      getParentNode()
-        .find('.rc-tree-node-content-wrapper')
-        .first()
-        .simulate('click');
+      getParentNode().find('.rc-tree-node-content-wrapper').first().simulate('click');
       expect(handleOnSelect).toHaveBeenCalledWith(
         ['0-0'],
         objectMatcher({
@@ -215,9 +206,7 @@ describe('Tree Props', () => {
 
     // Leaf select
     let node = convertNodePropsToEventData(getTargetNode().props());
-    getTargetNode()
-      .find('.rc-tree-node-content-wrapper')
-      .simulate('click');
+    getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
     expect(handleOnSelect).toHaveBeenCalledWith(
       ['0-0-0'],
       objectMatcher({
@@ -232,10 +221,7 @@ describe('Tree Props', () => {
 
     // Parent select
     node = convertNodePropsToEventData(getParentNode().props());
-    getParentNode()
-      .find('.rc-tree-node-content-wrapper')
-      .first()
-      .simulate('click');
+    getParentNode().find('.rc-tree-node-content-wrapper').first().simulate('click');
     expect(handleOnSelect).toHaveBeenCalledWith(
       ['0-0-0', '0-0'],
       objectMatcher({
@@ -250,9 +236,7 @@ describe('Tree Props', () => {
 
     // Leaf un-select
     node = convertNodePropsToEventData(getTargetNode().props());
-    getTargetNode()
-      .find('.rc-tree-node-content-wrapper')
-      .simulate('click');
+    getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
     expect(handleOnSelect).toHaveBeenCalledWith(
       ['0-0'],
       objectMatcher({
@@ -291,9 +275,7 @@ describe('Tree Props', () => {
 
       // Click leaf
       let node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode()
-        .find('.rc-tree-node-content-wrapper')
-        .simulate('click');
+      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
       expect(handleOnSelect).toHaveBeenCalledWith(
         ['0-0-0'],
         objectMatcher({
@@ -312,9 +294,7 @@ describe('Tree Props', () => {
 
       // Click checkbox
       node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode()
-        .find('.rc-tree-checkbox')
-        .simulate('click');
+      getTargetNode().find('.rc-tree-checkbox').simulate('click');
 
       expect(handleOnCheck).toHaveBeenCalledWith(
         ['0-0-0', '0-0'],
@@ -354,9 +334,7 @@ describe('Tree Props', () => {
 
       // Click leaf
       const node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode()
-        .find('.rc-tree-node-content-wrapper')
-        .simulate('click');
+      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
       expect(handleOnCheck).toHaveBeenCalledWith(
         ['0-0-0', '0-0'],
         objectMatcher({
@@ -379,18 +357,8 @@ describe('Tree Props', () => {
         </Tree>,
       );
 
-      expect(
-        wrapper
-          .find('TreeNode')
-          .at(0)
-          .find('.rc-tree-checkbox').length,
-      ).toBeTruthy();
-      expect(
-        wrapper
-          .find('TreeNode')
-          .at(1)
-          .find('.rc-tree-checkbox').length,
-      ).toBeFalsy();
+      expect(wrapper.find('TreeNode').at(0).find('.rc-tree-checkbox').length).toBeTruthy();
+      expect(wrapper.find('TreeNode').at(1).find('.rc-tree-checkbox').length).toBeFalsy();
     });
   });
 
@@ -398,7 +366,7 @@ describe('Tree Props', () => {
   describe('invalidate checkedKeys', () => {
     const errorSpy = spyError();
 
-    const genWrapper = checkedKeys =>
+    const genWrapper = (checkedKeys) =>
       mount(
         <Tree checkedKeys={checkedKeys} defaultExpandAll checkable>
           <TreeNode key="0-0">
@@ -442,9 +410,7 @@ describe('Tree Props', () => {
 
     // Click Leaf
     const node = convertNodePropsToEventData(getTargetNode().props());
-    getTargetNode()
-      .find('.rc-tree-checkbox')
-      .simulate('click');
+    getTargetNode().find('.rc-tree-checkbox').simulate('click');
     expect(handleOnCheck).toHaveBeenCalledWith(
       {
         checked: ['0-0-0'],
@@ -582,14 +548,8 @@ describe('Tree Props', () => {
       );
 
       // Do not trigger loadData
-      wrapper
-        .find('.rc-tree-switcher')
-        .at(0)
-        .simulate('click');
-      wrapper
-        .find('.rc-tree-switcher')
-        .at(0)
-        .simulate('click');
+      wrapper.find('.rc-tree-switcher').at(0).simulate('click');
+      wrapper.find('.rc-tree-switcher').at(0).simulate('click');
 
       return timeoutPromise().then(() => {
         expect(called).toBe(3);
@@ -617,7 +577,7 @@ describe('Tree Props', () => {
     });
 
     // https://github.com/ant-design/ant-design/issues/12464
-    it('by controlled', done => {
+    it('by controlled', (done) => {
       const treeData = [
         {
           title: 'demo1',
@@ -640,7 +600,7 @@ describe('Tree Props', () => {
           loadedKeys: [],
         };
 
-        onLoad = loadedKeys => {
+        onLoad = (loadedKeys) => {
           this.setState({ loadedKeys });
         };
 
@@ -668,10 +628,7 @@ describe('Tree Props', () => {
 
       setTimeout(() => {
         // Child click
-        wrapper
-          .find('.rc-tree-switcher')
-          .at(1)
-          .simulate('click');
+        wrapper.find('.rc-tree-switcher').at(1).simulate('click');
 
         setTimeout(() => {
           expect(count).toBe(2);
@@ -782,10 +739,12 @@ describe('Tree Props', () => {
           this.val = val;
         }
 
-        then = func => {
+        then = (func) => {
           const ret = func(this.val);
           return new FakePromise(ret);
         };
+
+        catch = () => {};
       }
 
       let wrapper;
@@ -822,7 +781,10 @@ describe('Tree Props', () => {
           {
             key: 'K11',
             title: 'T11',
-            children: [{ key: 'K110', title: 'T110' }, { key: 'K111', title: 'T111' }],
+            children: [
+              { key: 'K110', title: 'T110' },
+              { key: 'K111', title: 'T111' },
+            ],
           },
           { key: 'K12', title: 'T12' },
         ],
@@ -930,19 +892,7 @@ describe('Tree Props', () => {
       />,
     );
 
-    expect(
-      wrapper
-        .find('.rc-tree-title')
-        .first()
-        .find('.light-span')
-        .text(),
-    ).toEqual('light');
-    expect(
-      wrapper
-        .find('.rc-tree-title')
-        .last()
-        .find('.bamboo-span')
-        .text(),
-    ).toEqual('bamboo');
+    expect(wrapper.find('.rc-tree-title').first().find('.light-span').text()).toEqual('light');
+    expect(wrapper.find('.rc-tree-title').last().find('.bamboo-span').text()).toEqual('bamboo');
   });
 });

From 7af6abb2824ba2177517ce764da169604b1eee15 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 28 Jun 2021 18:38:24 +0800
Subject: [PATCH 187/397] 4.2.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f3946079..7657d2c1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.1.5",
+  "version": "4.2.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=12.x"

From 5cdbda55ac73300ed9de9fe40bc9e9b3867c3169 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 28 Jun 2021 22:56:40 +0800
Subject: [PATCH 188/397] Revert "fix: update role pos (#460)" (#471)

This reverts commit 2f6a514d18061855123a8583be15a1435710768e.
---
 src/NodeList.tsx                              |   4 +-
 src/Tree.tsx                                  |   1 -
 tests/__snapshots__/Tree.spec.js.snap         |  35 +++--
 .../__snapshots__/TreeNodeProps.spec.js.snap  |  45 ++++---
 tests/__snapshots__/TreeProps.spec.js.snap    | 125 +++++++++++-------
 5 files changed, 125 insertions(+), 85 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 90025c66..ca4a014b 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -170,7 +170,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
   const listRef = React.useRef<ListRef>(null);
   const indentMeasurerRef = React.useRef<HTMLDivElement>(null);
   React.useImperativeHandle(ref, () => ({
-    scrollTo: (scroll) => {
+    scrollTo: scroll => {
       listRef.current.scrollTo(scroll);
     },
     getIndentWidth: () => indentMeasurerRef.current.offsetWidth,
@@ -268,7 +268,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         </span>
       )}
 
-      <div>
+      <div role="tree">
         <input
           style={HIDDEN_STYLE}
           disabled={focusable === false || disabled}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index b1cb1d85..fc616db7 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -1319,7 +1319,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
         }}
       >
         <div
-          role="tree"
           className={classNames(prefixCls, className, {
             [`${prefixCls}-show-line`]: showLine,
             [`${prefixCls}-focused`]: focused,
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index c6ed3586..bf3589f7 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -3,9 +3,10 @@
 exports[`Tree Basic check basic render 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -136,9 +137,10 @@ exports[`Tree Basic check basic render 1`] = `
 exports[`Tree Basic check check after data ready works 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -238,9 +240,10 @@ exports[`Tree Basic check check after data ready works 1`] = `
 exports[`Tree Basic check check update when Tree trigger componentWillReceiveProps 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -309,9 +312,10 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
 exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -401,9 +405,10 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
 exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -549,9 +554,10 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
 exports[`Tree Basic renders correctly 1`] = `
 <div
   class="rc-tree forTest rc-tree-show-line"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -750,9 +756,10 @@ exports[`Tree Basic renders correctly 1`] = `
 exports[`Tree Basic renders opaque children correctly 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index 72c885df..ec30cdc8 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -3,9 +3,10 @@
 exports[`TreeNode Props className 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -182,9 +183,10 @@ exports[`TreeNode Props className 1`] = `
 exports[`TreeNode Props customize icon component 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -254,9 +256,10 @@ exports[`TreeNode Props customize icon component 1`] = `
 exports[`TreeNode Props customize icon element 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -326,9 +329,10 @@ exports[`TreeNode Props customize icon element 1`] = `
 exports[`TreeNode Props customize icon hide icon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -391,9 +395,10 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
 exports[`TreeNode Props data and aria props renders aria attributes on li 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -575,9 +580,10 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
 exports[`TreeNode Props data and aria props renders data attributes on li 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -759,9 +765,10 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
 exports[`TreeNode Props isLeaf 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -849,9 +856,10 @@ exports[`TreeNode Props isLeaf 1`] = `
 exports[`TreeNode Props isLeaf 2`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -945,9 +953,10 @@ exports[`TreeNode Props isLeaf 2`] = `
 exports[`TreeNode Props isLeaf 3`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index 80a7552d..f889b4c2 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -3,9 +3,10 @@
 exports[`Tree Props checkStrictly 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -105,9 +106,10 @@ exports[`Tree Props checkStrictly 1`] = `
 exports[`Tree Props checkable default 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -207,9 +209,10 @@ exports[`Tree Props checkable default 1`] = `
 exports[`Tree Props checkable without selectable 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -309,9 +312,10 @@ exports[`Tree Props checkable without selectable 1`] = `
 exports[`Tree Props custom switcher icon switcher icon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -461,9 +465,10 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
 exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -677,9 +682,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
 exports[`Tree Props data and aria props renders aria attributes 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -721,9 +727,10 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
 exports[`Tree Props data and aria props renders data attributes 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -765,9 +772,10 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
 exports[`Tree Props defaultExpandAll 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -861,9 +869,10 @@ exports[`Tree Props defaultExpandAll 1`] = `
 exports[`Tree Props disabled basic 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
@@ -930,9 +939,10 @@ exports[`Tree Props disabled basic 1`] = `
 exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
@@ -999,9 +1009,10 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
 exports[`Tree Props icon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1103,9 +1114,10 @@ exports[`Tree Props icon 1`] = `
 exports[`Tree Props invalidate checkedKeys null 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -1205,9 +1217,10 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
 exports[`Tree Props invalidate checkedKeys number 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -1307,9 +1320,10 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
 exports[`Tree Props loadData basic 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -1397,9 +1411,10 @@ exports[`Tree Props loadData basic 1`] = `
 exports[`Tree Props multiple 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1493,9 +1508,10 @@ exports[`Tree Props multiple 1`] = `
 exports[`Tree Props prefixCls 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1536,9 +1552,10 @@ exports[`Tree Props prefixCls 1`] = `
 exports[`Tree Props prefixCls 2`] = `
 <div
   class="test-prefix"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1579,9 +1596,10 @@ exports[`Tree Props prefixCls 2`] = `
 exports[`Tree Props selectable with selectable 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1675,9 +1693,10 @@ exports[`Tree Props selectable with selectable 1`] = `
 exports[`Tree Props selectable without selectable 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1771,9 +1790,10 @@ exports[`Tree Props selectable without selectable 1`] = `
 exports[`Tree Props showIcon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1863,9 +1883,10 @@ exports[`Tree Props showIcon 1`] = `
 exports[`Tree Props showIcon 2`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1949,9 +1970,10 @@ exports[`Tree Props showIcon 2`] = `
 exports[`Tree Props showIcon 3`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -2132,9 +2154,10 @@ exports[`Tree Props showIcon 3`] = `
 exports[`Tree Props showLine 1`] = `
 <div
   class="rc-tree rc-tree-show-line"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -2175,9 +2198,10 @@ exports[`Tree Props showLine 1`] = `
 exports[`Tree Props treeData 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
@@ -2413,9 +2437,10 @@ exports[`Tree Props treeData 1`] = `
 exports[`Tree Props treeData 2`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
-  <div>
+  <div
+    role="tree"
+  >
     <input
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"

From 427d6a8314a263cb1e784e7f36f5ddd1e7495722 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 28 Jun 2021 22:57:11 +0800
Subject: [PATCH 189/397] 4.2.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 7657d2c1..6977eb75 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.2.0",
+  "version": "4.2.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=12.x"

From a2a9f64c36e45e4fda3914658525922efd93f5ff Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 6 Jul 2021 10:48:43 +0800
Subject: [PATCH 190/397] revert: node version

close #472
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6977eb75..0958fabe 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
   "version": "4.2.1",
   "description": "tree ui component for react",
   "engines": {
-    "node": ">=12.x"
+    "node": ">=10.x"
   },
   "keywords": [
     "react",

From d4b903d1d8908fc09088cba1535452fcfff509e4 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 6 Jul 2021 10:49:33 +0800
Subject: [PATCH 191/397] 4.2.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0958fabe..01c807d0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.2.1",
+  "version": "4.2.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 3581d6e6b4661896216aa2fdb4d389be1c6b556b Mon Sep 17 00:00:00 2001
From: "dependabot-preview[bot]"
 <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date: Mon, 12 Jul 2021 19:18:36 +0800
Subject: [PATCH 192/397] chore(deps-dev): bump rc-tooltip from 4.2.3 to 5.1.1
 (#452)

Bumps [rc-tooltip](https://github.com/react-component/tooltip) from 4.2.3 to 5.1.1.
- [Release notes](https://github.com/react-component/tooltip/releases)
- [Changelog](https://github.com/react-component/tooltip/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-component/tooltip/compare/v4.2.3...v5.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 01c807d0..e63ddf0c 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
     "less": "^3.11.1",
     "np": "^6.0.0",
     "rc-dialog": "^8.1.0",
-    "rc-tooltip": "4.x",
+    "rc-tooltip": "5.x",
     "rc-trigger": "^5.0.7",
     "react": "^16.8.0",
     "react-dom": "^16.8.0",

From 09375a08ad104e80fff88c9363f4eb807d4547f2 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 12 Jul 2021 19:29:58 +0800
Subject: [PATCH 193/397] fix: Tree role attribute (#473)

* Revert "Revert "fix: update role pos (#460)" (#471)"

This reverts commit 5cdbda55ac73300ed9de9fe40bc9e9b3867c3169.

* fix `Form elements must have labels`

* update snapshot
---
 src/NodeList.tsx                              |   5 +-
 src/Tree.tsx                                  |   1 +
 tests/__snapshots__/Tree.spec.js.snap         |  42 ++---
 .../__snapshots__/TreeNodeProps.spec.js.snap  |  54 +++----
 tests/__snapshots__/TreeProps.spec.js.snap    | 150 +++++++++---------
 5 files changed, 127 insertions(+), 125 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index ca4a014b..dd8abd04 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -170,7 +170,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
   const listRef = React.useRef<ListRef>(null);
   const indentMeasurerRef = React.useRef<HTMLDivElement>(null);
   React.useImperativeHandle(ref, () => ({
-    scrollTo: scroll => {
+    scrollTo: (scroll) => {
       listRef.current.scrollTo(scroll);
     },
     getIndentWidth: () => indentMeasurerRef.current.offsetWidth,
@@ -268,7 +268,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         </span>
       )}
 
-      <div role="tree">
+      <div>
         <input
           style={HIDDEN_STYLE}
           disabled={focusable === false || disabled}
@@ -278,6 +278,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
           onBlur={onBlur}
           value=""
           onChange={noop}
+          aria-label="for screen reader"
         />
       </div>
 
diff --git a/src/Tree.tsx b/src/Tree.tsx
index fc616db7..b1cb1d85 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -1319,6 +1319,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         }}
       >
         <div
+          role="tree"
           className={classNames(prefixCls, className, {
             [`${prefixCls}-show-line`]: showLine,
             [`${prefixCls}-focused`]: focused,
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index bf3589f7..aba2cf5f 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -3,11 +3,11 @@
 exports[`Tree Basic check basic render 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -137,11 +137,11 @@ exports[`Tree Basic check basic render 1`] = `
 exports[`Tree Basic check check after data ready works 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
@@ -240,11 +240,11 @@ exports[`Tree Basic check check after data ready works 1`] = `
 exports[`Tree Basic check check update when Tree trigger componentWillReceiveProps 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
@@ -312,11 +312,11 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
 exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
@@ -405,11 +405,11 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
 exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
@@ -554,11 +554,11 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
 exports[`Tree Basic renders correctly 1`] = `
 <div
   class="rc-tree forTest rc-tree-show-line"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -756,11 +756,11 @@ exports[`Tree Basic renders correctly 1`] = `
 exports[`Tree Basic renders opaque children correctly 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index ec30cdc8..e2f43eb8 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -3,11 +3,11 @@
 exports[`TreeNode Props className 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -183,11 +183,11 @@ exports[`TreeNode Props className 1`] = `
 exports[`TreeNode Props customize icon component 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -256,11 +256,11 @@ exports[`TreeNode Props customize icon component 1`] = `
 exports[`TreeNode Props customize icon element 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -329,11 +329,11 @@ exports[`TreeNode Props customize icon element 1`] = `
 exports[`TreeNode Props customize icon hide icon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -395,11 +395,11 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
 exports[`TreeNode Props data and aria props renders aria attributes on li 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -580,11 +580,11 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
 exports[`TreeNode Props data and aria props renders data attributes on li 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -765,11 +765,11 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
 exports[`TreeNode Props isLeaf 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -856,11 +856,11 @@ exports[`TreeNode Props isLeaf 1`] = `
 exports[`TreeNode Props isLeaf 2`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -953,11 +953,11 @@ exports[`TreeNode Props isLeaf 2`] = `
 exports[`TreeNode Props isLeaf 3`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index f889b4c2..82b0be4f 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -3,11 +3,11 @@
 exports[`Tree Props checkStrictly 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
@@ -106,11 +106,11 @@ exports[`Tree Props checkStrictly 1`] = `
 exports[`Tree Props checkable default 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -209,11 +209,11 @@ exports[`Tree Props checkable default 1`] = `
 exports[`Tree Props checkable without selectable 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -312,11 +312,11 @@ exports[`Tree Props checkable without selectable 1`] = `
 exports[`Tree Props custom switcher icon switcher icon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -465,11 +465,11 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
 exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -682,11 +682,11 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
 exports[`Tree Props data and aria props renders aria attributes 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -727,11 +727,11 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
 exports[`Tree Props data and aria props renders data attributes 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -772,11 +772,11 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
 exports[`Tree Props defaultExpandAll 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -869,11 +869,11 @@ exports[`Tree Props defaultExpandAll 1`] = `
 exports[`Tree Props disabled basic 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -939,11 +939,11 @@ exports[`Tree Props disabled basic 1`] = `
 exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       disabled=""
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
@@ -1009,11 +1009,11 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
 exports[`Tree Props icon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -1114,11 +1114,11 @@ exports[`Tree Props icon 1`] = `
 exports[`Tree Props invalidate checkedKeys null 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
@@ -1217,11 +1217,11 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
 exports[`Tree Props invalidate checkedKeys number 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
@@ -1320,11 +1320,11 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
 exports[`Tree Props loadData basic 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
@@ -1411,11 +1411,11 @@ exports[`Tree Props loadData basic 1`] = `
 exports[`Tree Props multiple 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -1508,11 +1508,11 @@ exports[`Tree Props multiple 1`] = `
 exports[`Tree Props prefixCls 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -1552,11 +1552,11 @@ exports[`Tree Props prefixCls 1`] = `
 exports[`Tree Props prefixCls 2`] = `
 <div
   class="test-prefix"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -1596,11 +1596,11 @@ exports[`Tree Props prefixCls 2`] = `
 exports[`Tree Props selectable with selectable 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -1693,11 +1693,11 @@ exports[`Tree Props selectable with selectable 1`] = `
 exports[`Tree Props selectable without selectable 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -1790,11 +1790,11 @@ exports[`Tree Props selectable without selectable 1`] = `
 exports[`Tree Props showIcon 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -1883,11 +1883,11 @@ exports[`Tree Props showIcon 1`] = `
 exports[`Tree Props showIcon 2`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -1970,11 +1970,11 @@ exports[`Tree Props showIcon 2`] = `
 exports[`Tree Props showIcon 3`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -2154,11 +2154,11 @@ exports[`Tree Props showIcon 3`] = `
 exports[`Tree Props showLine 1`] = `
 <div
   class="rc-tree rc-tree-show-line"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
       tabindex="0"
       value=""
@@ -2198,11 +2198,11 @@ exports[`Tree Props showLine 1`] = `
 exports[`Tree Props treeData 1`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
@@ -2437,11 +2437,11 @@ exports[`Tree Props treeData 1`] = `
 exports[`Tree Props treeData 2`] = `
 <div
   class="rc-tree"
+  role="tree"
 >
-  <div
-    role="tree"
-  >
+  <div>
     <input
+      aria-label="for screen reader"
       style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""

From e47a62f1b67c496bb9d82422e7f1ee2a76cf3c5d Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 12 Jul 2021 19:31:59 +0800
Subject: [PATCH 194/397] 4.3.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index e63ddf0c..50d92a31 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.2.2",
+  "version": "4.3.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 89071a3e88522e7d8e692a1b975a2cfc247da3ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Wed, 14 Jul 2021 10:04:14 +0800
Subject: [PATCH 195/397] feat: fieldNames support (#468)

* feat: init fieldNames

* fix: expanded logic

* fix: select logic

* fix: default fieldNames check

* fix: Motion logic

* chore: Fix part of util

* fix: compatiable logic

* fix: flatten logic

* test: fieldName test case

* test: Dynamic change of fieldNames
---
 .prettierrc                  |   3 +-
 docs/demo/fieldNames.md      |   3 +
 docs/examples/fieldNames.tsx |  39 ++++++++++++
 package.json                 |   1 +
 src/MotionTreeNode.tsx       |   5 +-
 src/NodeList.tsx             |  10 ++-
 src/Tree.tsx                 |  74 ++++++++++++++--------
 src/TreeNode.tsx             |   2 +-
 src/interface.tsx            |   8 +++
 src/utils/treeUtil.ts        |  65 +++++++++++++-------
 tests/FieldNames.spec.tsx    | 115 +++++++++++++++++++++++++++++++++++
 tests/Tree.spec.js           |  24 ++++----
 tests/TreeProps.spec.js      |  10 +--
 13 files changed, 291 insertions(+), 68 deletions(-)
 create mode 100644 docs/demo/fieldNames.md
 create mode 100644 docs/examples/fieldNames.tsx
 create mode 100644 tests/FieldNames.spec.tsx

diff --git a/.prettierrc b/.prettierrc
index f307fb19..7f12e041 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -4,5 +4,6 @@
   "singleQuote": true,
   "tabWidth": 2,
   "trailingComma": "all",
-  "printWidth": 100
+  "printWidth": 100,
+  "arrowParens": "avoid"
 }
diff --git a/docs/demo/fieldNames.md b/docs/demo/fieldNames.md
new file mode 100644
index 00000000..2d34a1c1
--- /dev/null
+++ b/docs/demo/fieldNames.md
@@ -0,0 +1,3 @@
+## Field Names
+
+<code src="../examples/fieldNames.tsx">
diff --git a/docs/examples/fieldNames.tsx b/docs/examples/fieldNames.tsx
new file mode 100644
index 00000000..db93f661
--- /dev/null
+++ b/docs/examples/fieldNames.tsx
@@ -0,0 +1,39 @@
+/* eslint-disable no-alert, no-console, react/no-find-dom-node */
+import React from 'react';
+import '../../assets/index.less';
+import './basic.less';
+import Tree from 'rc-tree';
+
+export default () => {
+  return (
+    <Tree
+      // treeData={
+      //   [
+      //     {
+      //       fieldLabel: 'Root',
+      //       fieldKey: 'root',
+      //       fieldChildren: [
+      //         {
+      //           fieldLabel: 'Bamboo',
+      //           fieldKey: 'bamboo',
+      //         },
+      //         {
+      //           fieldLabel: 'Light',
+      //           fieldKey: 'light',
+      //         },
+      //       ],
+      //     },
+      //   ] as any
+      // }
+
+      // fieldNames={{
+      //   title: 'fieldLabel',
+      //   key: 'fieldKey',
+      //   children: 'fieldChildren',
+      // }}
+      defaultExpandAll
+    >
+      <Tree.TreeNode title="Bamboo" key="light" />
+    </Tree>
+  );
+};
diff --git a/package.json b/package.json
index 50d92a31..b52f1aae 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
     "react-dom": "*"
   },
   "devDependencies": {
+    "@types/enzyme": "^3.10.9",
     "@types/jest": "^26.0.4",
     "@types/react": "^17.0.11",
     "@types/react-dom": "^17.0.8",
diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 8169c8b6..d7939bfa 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -82,7 +82,9 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
           >
             {motionNodes.map((treeNode: FlattenNode) => {
               const {
-                data: { key, ...restProps },
+                data: { ...restProps },
+                title,
+                key,
                 isStart,
                 isEnd,
               } = treeNode;
@@ -94,6 +96,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
                 <TreeNode
                   {...restProps}
                   {...treeNodeProps}
+                  title={title}
                   active={active}
                   data={treeNode.data}
                   key={key}
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index dd8abd04..dfb55097 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -41,6 +41,8 @@ const MotionFlattenData: FlattenNode = {
   children: [],
   pos: MotionEntity.pos,
   data: MotionNode,
+  title: null,
+  key: MOTION_KEY,
   /** Hold empty list here since we do not use it */
   isStart: [],
   isEnd: [],
@@ -170,7 +172,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
   const listRef = React.useRef<ListRef>(null);
   const indentMeasurerRef = React.useRef<HTMLDivElement>(null);
   React.useImperativeHandle(ref, () => ({
-    scrollTo: (scroll) => {
+    scrollTo: scroll => {
       listRef.current.scrollTo(scroll);
     },
     getIndentWidth: () => indentMeasurerRef.current.offsetWidth,
@@ -312,11 +314,14 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         {(treeNode: FlattenNode) => {
           const {
             pos,
-            data: { key, ...restProps },
+            data: { ...restProps },
+            title,
+            key,
             isStart,
             isEnd,
           } = treeNode;
           const mergedKey = getKey(key, pos);
+          delete restProps.key;
           delete restProps.children;
 
           const treeNodeProps = getTreeNodeProps(mergedKey, treeNodeRequiredProps);
@@ -325,6 +330,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
             <MotionTreeNode
               {...restProps}
               {...treeNodeProps}
+              title={title}
               active={!!activeItem && key === activeItem.data.key}
               pos={pos}
               data={treeNode.data}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index b1cb1d85..9077d00e 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -34,6 +34,7 @@ import {
   NodeInstance,
   ScrollTo,
   Direction,
+  FieldNames,
 } from './interface';
 import {
   flattenTreeData,
@@ -42,6 +43,7 @@ import {
   warningWithoutKey,
   convertNodePropsToEventData,
   getTreeNodeProps,
+  fillFieldNames,
 } from './utils/treeUtil';
 import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
 import TreeNode from './TreeNode';
@@ -68,6 +70,7 @@ export interface TreeProps {
   tabIndex?: number;
   children?: React.ReactNode;
   treeData?: DataNode[]; // Generate treeNode by children
+  fieldNames?: FieldNames;
   showLine?: boolean;
   showIcon?: boolean;
   icon?: IconType;
@@ -197,6 +200,8 @@ interface TreeState {
   listChanging: boolean;
 
   prevProps: TreeProps;
+
+  fieldNames: FieldNames;
 }
 
 class Tree extends React.Component<TreeProps, TreeState> {
@@ -264,6 +269,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
     listChanging: false,
 
     prevProps: null,
+
+    fieldNames: fillFieldNames(),
   };
 
   dragStartMousePosition = null;
@@ -290,6 +297,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
     // ================== Tree Node ==================
     let treeData: DataNode[];
 
+    // fieldNames
+    let { fieldNames } = prevState;
+    if (needSync('fieldNames')) {
+      fieldNames = fillFieldNames(props.fieldNames);
+      newState.fieldNames = fieldNames;
+    }
+
     // Check if `treeData` or `children` changed and save into the state.
     if (needSync('treeData')) {
       ({ treeData } = props);
@@ -301,7 +315,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     // Save flatten nodes info and convert `treeData` into keyEntities
     if (treeData) {
       newState.treeData = treeData;
-      const entitiesMap = convertDataToEntities(treeData);
+      const entitiesMap = convertDataToEntities(treeData, { fieldNames });
       newState.keyEntities = {
         [MOTION_KEY]: MotionEntity,
         ...entitiesMap.keyEntities,
@@ -309,7 +323,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
       // Warning if treeNode not provide key
       if (process.env.NODE_ENV !== 'production') {
-        warningWithoutKey(treeData);
+        warningWithoutKey(treeData, fieldNames);
       }
     }
 
@@ -324,7 +338,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     } else if (!prevProps && props.defaultExpandAll) {
       const cloneKeyEntities = { ...keyEntities };
       delete cloneKeyEntities[MOTION_KEY];
-      newState.expandedKeys = Object.keys(cloneKeyEntities).map((key) => cloneKeyEntities[key].key);
+      newState.expandedKeys = Object.keys(cloneKeyEntities).map(key => cloneKeyEntities[key].key);
     } else if (!prevProps && props.defaultExpandedKeys) {
       newState.expandedKeys =
         props.autoExpandParent || props.defaultExpandParent
@@ -341,6 +355,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       const flattenNodes: FlattenNode[] = flattenTreeData(
         treeData || prevState.treeData,
         newState.expandedKeys || prevState.expandedKeys,
+        fieldNames,
       );
       newState.flattenNodes = flattenNodes;
     }
@@ -476,7 +491,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     if (!this.delayedDragEnterLogic) {
       this.delayedDragEnterLogic = {};
     }
-    Object.keys(this.delayedDragEnterLogic).forEach((key) => {
+    Object.keys(this.delayedDragEnterLogic).forEach(key => {
       clearTimeout(this.delayedDragEnterLogic[key]);
     });
 
@@ -637,7 +652,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   // since stopPropagation() is called in treeNode
   // if onWindowDrag is called, whice means state is keeped, drag state should be cleared
-  onWindowDragEnd = (event) => {
+  onWindowDragEnd = event => {
     this.onNodeDragEnd(event, null, true);
     window.removeEventListener('dragend', this.onWindowDragEnd);
   };
@@ -659,8 +674,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeDrop = (event: React.MouseEvent<HTMLDivElement>, node, outsideTree: boolean = false) => {
-    const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } =
-      this.state;
+    const {
+      dragChildrenKeys,
+      dropPosition,
+      dropTargetKey,
+      dropTargetPos,
+      dropAllowed,
+    } = this.state;
 
     if (!dropAllowed) return;
 
@@ -735,9 +755,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   onNodeSelect: NodeMouseEventHandler = (e, treeNode) => {
     let { selectedKeys } = this.state;
-    const { keyEntities } = this.state;
+    const { keyEntities, fieldNames } = this.state;
     const { onSelect, multiple } = this.props;
-    const { selected, key } = treeNode;
+    const { selected } = treeNode;
+    const key = treeNode[fieldNames.key];
     const targetSelected = !selected;
 
     // Update selected keys
@@ -751,13 +772,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
     // [Legacy] Not found related usage in doc or upper libs
     const selectedNodes = selectedKeys
-      .map((selectedKey) => {
+      .map(selectedKey => {
         const entity = keyEntities[selectedKey];
         if (!entity) return null;
 
         return entity.node;
       })
-      .filter((node) => node);
+      .filter(node => node);
 
     this.setUncontrolledState({ selectedKeys });
 
@@ -800,9 +821,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
       checkedObj = { checked: checkedKeys, halfChecked: halfCheckedKeys };
 
       eventObj.checkedNodes = checkedKeys
-        .map((checkedKey) => keyEntities[checkedKey])
-        .filter((entity) => entity)
-        .map((entity) => entity.node);
+        .map(checkedKey => keyEntities[checkedKey])
+        .filter(entity => entity)
+        .map(entity => entity.node);
 
       this.setUncontrolledState({ checkedKeys });
     } else {
@@ -831,7 +852,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       eventObj.checkedNodesPositions = [];
       eventObj.halfCheckedKeys = halfCheckedKeys;
 
-      checkedKeys.forEach((checkedKey) => {
+      checkedKeys.forEach(checkedKey => {
         const entity = keyEntities[checkedKey];
         if (!entity) return;
 
@@ -894,7 +915,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
             resolve();
           })
-          .catch((e) => {
+          .catch(e => {
             const { loadingKeys: currentLoadingKeys } = this.state;
             const newLoadingKeys = arrDel(currentLoadingKeys, key);
             this.setState({
@@ -978,9 +999,9 @@ class Tree extends React.Component<TreeProps, TreeState> {
   // =========================== Expanded ===========================
   /** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
   setExpandedKeys = (expandedKeys: Key[]) => {
-    const { treeData } = this.state;
+    const { treeData, fieldNames } = this.state;
 
-    const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys);
+    const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys, fieldNames);
     this.setUncontrolledState(
       {
         expandedKeys,
@@ -992,9 +1013,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
     let { expandedKeys } = this.state;
-    const { listChanging } = this.state;
+    const { listChanging, fieldNames } = this.state;
     const { onExpand, loadData } = this.props;
-    const { key, expanded } = treeNode;
+    const { expanded } = treeNode;
+    const key = treeNode[fieldNames.key];
 
     // Do nothing when motion is in progress
     if (listChanging) {
@@ -1033,7 +1055,11 @@ class Tree extends React.Component<TreeProps, TreeState> {
         loadPromise
           .then(() => {
             // [Legacy] Refresh logic
-            const newFlattenTreeData = flattenTreeData(this.state.treeData, expandedKeys);
+            const newFlattenTreeData = flattenTreeData(
+              this.state.treeData,
+              expandedKeys,
+              fieldNames,
+            );
             this.setUncontrolledState({ flattenNodes: newFlattenTreeData });
           })
           .catch(() => {
@@ -1108,7 +1134,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
+  onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
     const { activeKey, expandedKeys, checkedKeys } = this.state;
     const { onKeyDown, checkable, selectable } = this.props;
 
@@ -1210,7 +1236,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     let allPassed = true;
     const newState = {};
 
-    Object.keys(state).forEach((name) => {
+    Object.keys(state).forEach(name => {
       if (name in this.props) {
         allPassed = false;
         return;
@@ -1228,7 +1254,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  scrollTo: ScrollTo = (scroll) => {
+  scrollTo: ScrollTo = scroll => {
     this.listRef.current.scrollTo(scroll);
   };
 
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 98485b0b..f42c4bdd 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -293,7 +293,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
 
     if (loading) {
       return;
-    };
+    }
 
     // read from state to avoid loadData at same time
     if (loadData && expanded && !this.isLeaf()) {
diff --git a/src/interface.tsx b/src/interface.tsx
index 5c903476..b537a5e2 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -70,6 +70,8 @@ export interface FlattenNode {
   children: FlattenNode[];
   pos: string;
   data: DataNode;
+  title: React.ReactNode;
+  key: Key;
   isStart: boolean[];
   isEnd: boolean[];
 }
@@ -79,3 +81,9 @@ export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
 export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;
 
 export type Direction = 'ltr' | 'rtl' | undefined;
+
+export interface FieldNames {
+  title?: string;
+  key?: string;
+  children?: string;
+}
\ No newline at end of file
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index c71fb92b..e6290711 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -1,4 +1,5 @@
 import * as React from 'react';
+import omit from 'rc-util/lib/omit';
 import toArray from 'rc-util/lib/Children/toArray';
 import warning from 'rc-util/lib/warning';
 import {
@@ -9,6 +10,7 @@ import {
   Key,
   EventDataNode,
   GetKey,
+  FieldNames,
 } from '../interface';
 import { getPosition, isTreeNode } from '../util';
 import { TreeNodeProps } from '../TreeNode';
@@ -20,15 +22,26 @@ export function getKey(key: Key, pos: string) {
   return pos;
 }
 
+export function fillFieldNames(fieldNames?: FieldNames) {
+  const { title, key, children } = fieldNames || {};
+
+  return {
+    title: title || 'title',
+    key: key || 'key',
+    children: children || 'children',
+  };
+}
+
 /**
  * Warning if TreeNode do not provides key
  */
-export function warningWithoutKey(treeData: DataNode[] = []) {
+export function warningWithoutKey(treeData: DataNode[], fieldNames: FieldNames) {
   const keys: Map<string, boolean> = new Map();
 
   function dig(list: DataNode[], path: string = '') {
     (list || []).forEach(treeNode => {
-      const { key, children } = treeNode;
+      const key = treeNode[fieldNames.key];
+      const children = treeNode[fieldNames.children];
       warning(
         key !== null && key !== undefined,
         `Tree node must have a certain key: [${path}${key}]`,
@@ -90,20 +103,25 @@ export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
  * need expanded keys, provides `true` means all expanded (used in `rc-tree-select`).
  */
 export function flattenTreeData(
-  treeNodeList: DataNode[] = [],
-  expandedKeys: Key[] | true = [],
+  treeNodeList: DataNode[],
+  expandedKeys: Key[] | true,
+  fieldNames: FieldNames,
 ): FlattenNode[] {
+  const { title: fieldTitle, key: fieldKey, children: fieldChildren } = fillFieldNames(fieldNames);
+
   const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys);
   const flattenList: FlattenNode[] = [];
 
   function dig(list: DataNode[], parent: FlattenNode = null): FlattenNode[] {
     return list.map((treeNode, index) => {
       const pos: string = getPosition(parent ? parent.pos : '0', index);
-      const mergedKey = getKey(treeNode.key, pos);
+      const mergedKey = getKey(treeNode[fieldKey], pos);
 
       // Add FlattenDataNode into list
       const flattenNode: FlattenNode = {
-        ...treeNode,
+        ...omit(treeNode, [fieldTitle, fieldKey, fieldChildren] as any),
+        title: treeNode[fieldTitle],
+        key: mergedKey,
         parent,
         pos,
         children: null,
@@ -116,7 +134,7 @@ export function flattenTreeData(
 
       // Loop treeNode children
       if (expandedKeys === true || expandedKeySet.has(mergedKey)) {
-        flattenNode.children = dig(treeNode.children || [], flattenNode);
+        flattenNode.children = dig(treeNode[fieldChildren] || [], flattenNode);
       } else {
         flattenNode.children = [];
       }
@@ -135,6 +153,7 @@ type ExternalGetKey = GetKey<DataNode> | string;
 interface TraverseDataNodesConfig {
   childrenPropName?: string;
   externalGetKey?: ExternalGetKey;
+  fieldNames?: FieldNames;
 }
 
 /**
@@ -152,22 +171,22 @@ export function traverseDataNodes(
     level: number;
   }) => void,
   // To avoid too many params, let use config instead of origin param
-  config?: TraverseDataNodesConfig | ExternalGetKey,
+  config?: TraverseDataNodesConfig | string,
 ) {
-  // Init config
-  let externalGetKey: ExternalGetKey = null;
-  let childrenPropName: string;
+  let mergedConfig: TraverseDataNodesConfig = {};
+  if (typeof config === 'object') {
+    mergedConfig = config;
+  } else {
+    mergedConfig = { externalGetKey: config };
+  }
+  mergedConfig = mergedConfig || {};
 
-  const configType = typeof config;
+  // Init config
+  const { childrenPropName, externalGetKey, fieldNames } = mergedConfig;
 
-  if (configType === 'function' || configType === 'string') {
-    // Legacy getKey param
-    externalGetKey = config as ExternalGetKey;
-  } else if (config && configType === 'object') {
-    ({ childrenPropName, externalGetKey } = config as TraverseDataNodesConfig);
-  }
+  const { key: fieldKey, children: fieldChildren } = fillFieldNames(fieldNames);
 
-  childrenPropName = childrenPropName || 'children';
+  const mergeChildrenPropName = childrenPropName || fieldChildren;
 
   // Get keys
   let syntheticGetKey: (node: DataNode, pos?: string) => Key;
@@ -178,7 +197,7 @@ export function traverseDataNodes(
       syntheticGetKey = (node: DataNode) => (externalGetKey as GetKey<DataNode>)(node);
     }
   } else {
-    syntheticGetKey = (node, pos) => getKey(node.key, pos);
+    syntheticGetKey = (node, pos) => getKey(node[fieldKey], pos);
   }
 
   // Process
@@ -187,7 +206,7 @@ export function traverseDataNodes(
     index?: number,
     parent?: { node: DataNode; pos: string; level: number },
   ) {
-    const children = node ? node[childrenPropName] : dataNodes;
+    const children = node ? node[mergeChildrenPropName] : dataNodes;
     const pos = node ? getPosition(parent.pos, index) : '0';
 
     // Process node if is not root
@@ -236,12 +255,14 @@ export function convertDataToEntities(
     onProcessFinished,
     externalGetKey,
     childrenPropName,
+    fieldNames,
   }: {
     initWrapper?: (wrapper: Wrapper) => Wrapper;
     processEntity?: (entity: DataEntity, wrapper: Wrapper) => void;
     onProcessFinished?: (wrapper: Wrapper) => void;
     externalGetKey?: ExternalGetKey;
     childrenPropName?: string;
+    fieldNames?: FieldNames;
   } = {},
   /** @deprecated Use `config.externalGetKey` instead */
   legacyExternalGetKey?: ExternalGetKey,
@@ -282,7 +303,7 @@ export function convertDataToEntities(
         processEntity(entity, wrapper);
       }
     },
-    { externalGetKey: mergedExternalGetKey, childrenPropName },
+    { externalGetKey: mergedExternalGetKey, childrenPropName, fieldNames },
   );
 
   if (onProcessFinished) {
diff --git a/tests/FieldNames.spec.tsx b/tests/FieldNames.spec.tsx
new file mode 100644
index 00000000..3501f451
--- /dev/null
+++ b/tests/FieldNames.spec.tsx
@@ -0,0 +1,115 @@
+/* eslint-disable no-undef, react/no-multi-comp */
+import React from 'react';
+import { mount } from 'enzyme';
+import Tree from '../src';
+import { InternalTreeNode } from '../src/TreeNode';
+import { spyConsole } from './util';
+
+describe('FieldNames', () => {
+  spyConsole();
+
+  it('customize fieldNames', () => {
+    const wrapper = mount(
+      <Tree
+        treeData={
+          [
+            {
+              myTitle: 'Title',
+              myKey: 'title',
+              myChildren: [
+                { myTitle: 'Sub1', myKey: 'sub_1' },
+                { myTitle: 'Sub2', myKey: 'sub_2' },
+              ],
+            },
+          ] as any
+        }
+        defaultExpandAll
+        fieldNames={{
+          title: 'myTitle',
+          key: 'myKey',
+          children: 'myChildren',
+        }}
+      />,
+    );
+
+    // Title
+    const titleList = wrapper
+      .find('.rc-tree-list-holder div.rc-tree-treenode')
+      .map(node => node.text());
+
+    expect(titleList).toEqual(['Title', 'Sub1', 'Sub2']);
+
+    // Key
+    const keyList = wrapper.find(InternalTreeNode).map(node => node.props().eventKey);
+
+    expect(keyList).toEqual(['title', 'sub_1', 'sub_2']);
+  });
+
+  it('dynamic change fieldNames', () => {
+    const wrapper = mount(
+      <Tree
+        defaultExpandAll
+        treeData={[
+          {
+            title: 'Origin Title',
+            key: 'parent',
+            children: [
+              {
+                title: 'Origin Sub 1',
+                key: 'sub_1',
+              },
+              {
+                title: 'Origin Sub 2',
+                key: 'sub_2',
+              },
+            ],
+          },
+        ]}
+      />,
+    );
+
+    // Title
+    expect(
+      wrapper.find('.rc-tree-list-holder div.rc-tree-treenode').map(node => node.text()),
+    ).toEqual(['Origin Title', 'Origin Sub 1', 'Origin Sub 2']);
+
+    // Change it
+    wrapper.setProps({
+      treeData: [
+        {
+          myTitle: 'New Title',
+          myKey: 'parent',
+          myChildren: [
+            {
+              myTitle: 'New Sub 1',
+              myKey: 'sub_1',
+            },
+            {
+              myTitle: 'New Sub 2',
+              myKey: 'sub_2',
+            },
+          ],
+        },
+        {
+          myTitle: 'New Title 2',
+          myKey: 'parent2',
+          myChildren: [
+            {
+              myTitle: 'New Sub 3',
+              myKey: 'sub_3',
+            },
+          ],
+        },
+      ],
+      fieldNames: {
+        title: 'myTitle',
+        key: 'myKey',
+        children: 'myChildren',
+      },
+    });
+
+    expect(
+      wrapper.find('.rc-tree-list-holder div.rc-tree-treenode').map(node => node.text()),
+    ).toEqual(['New Title', 'New Sub 1', 'New Sub 2', 'New Title 2']);
+  });
+});
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 308b737c..186fd7a7 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -15,8 +15,8 @@ const OPEN_CLASSNAME = '.rc-tree-switcher_open';
 const CHECKED_CLASSNAME = '.rc-tree-checkbox-checked';
 const SELECTED_CLASSNAME = '.rc-tree-node-selected';
 
-const delay = (timeout) =>
-  new Promise((resolve) => {
+const delay = timeout =>
+  new Promise(resolve => {
     setTimeout(resolve, timeout);
   });
 
@@ -238,7 +238,7 @@ describe('Tree Basic', () => {
         </Tree>,
       );
       wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach((checkbox) => {
+      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
         expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
       });
     });
@@ -315,7 +315,7 @@ describe('Tree Basic', () => {
       wrapper.find('.rc-tree-switcher').simulate('click');
       wrapper.find('.rc-tree-checkbox').last().simulate('click');
       wrapper.find('.rc-tree-checkbox').first().simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach((checkbox) => {
+      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
         expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
       });
     });
@@ -414,7 +414,7 @@ describe('Tree Basic', () => {
     // https://github.com/ant-design/ant-design/issues/7353
     it('check children after changing from children[disableCheckbox] from true to false', () => {
       let checkedKeys = null;
-      const mockHandleCheck = (keys) => (checkedKeys = keys);
+      const mockHandleCheck = keys => (checkedKeys = keys);
       function Test({ disableCheckbox }) {
         return (
           <Tree checkable onCheck={mockHandleCheck}>
@@ -536,7 +536,7 @@ describe('Tree Basic', () => {
             },
           };
 
-          handleCheck = (checkedKeys) => {
+          handleCheck = checkedKeys => {
             this.setState({ checkedKeys });
           };
 
@@ -969,7 +969,7 @@ describe('Tree Basic', () => {
 
     describe('full steps', () => {
       function dropTarget(targetSelector) {
-        return new Promise((resolve) => {
+        return new Promise(resolve => {
           const wrapper = mount(
             <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
               <TreeNode key="0-0" className="dragTarget">
@@ -1066,7 +1066,7 @@ describe('Tree Basic', () => {
       afterEach(() => {
         domSpy.mockRestore();
       });
-      ['ltr', 'rtl'].forEach((dir) => {
+      ['ltr', 'rtl'].forEach(dir => {
         const base = dir === 'ltr' ? 1 : -1;
         it('not allow cross level dnd on expanded nodes', () => {
           const onDrop = jest.fn();
@@ -1537,9 +1537,9 @@ describe('Tree Basic', () => {
     expect(() => {
       mount(
         <Tree>
-          {[0, 1].map((i) => (
+          {[0, 1].map(i => (
             <TreeNode title={i} key={i}>
-              {[2, 3].map((j) => (
+              {[2, 3].map(j => (
                 <TreeNode title={j} key={`${i}_${j}`} />
               ))}
               <TreeNode title="4" key={`${i}_4`} />
@@ -1554,7 +1554,7 @@ describe('Tree Basic', () => {
     const wrapper = render(
       <Tree>
         <TreeNode title="0" key="0">
-          {[1, 2].map((i) => (
+          {[1, 2].map(i => (
             <TreeNode title={i} key={i} />
           ))}
           <TreeNode title="3" key="3" />
@@ -1612,7 +1612,7 @@ describe('Tree Basic', () => {
           checkable
           defaultExpandAll
           checkedKeys={checkedKeys}
-          onCheck={(keys) => {
+          onCheck={keys => {
             setCheckedKeys(keys);
             onCheck(keys);
           }}
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index 6797fa27..ba67b3a9 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -14,7 +14,7 @@ import { convertNodePropsToEventData } from '../src/utils/treeUtil';
 
 // Promisify timeout to let jest catch works
 function timeoutPromise(delay = 0) {
-  return new Promise((resolve) => {
+  return new Promise(resolve => {
     setTimeout(resolve, delay);
   });
 }
@@ -366,7 +366,7 @@ describe('Tree Props', () => {
   describe('invalidate checkedKeys', () => {
     const errorSpy = spyError();
 
-    const genWrapper = (checkedKeys) =>
+    const genWrapper = checkedKeys =>
       mount(
         <Tree checkedKeys={checkedKeys} defaultExpandAll checkable>
           <TreeNode key="0-0">
@@ -577,7 +577,7 @@ describe('Tree Props', () => {
     });
 
     // https://github.com/ant-design/ant-design/issues/12464
-    it('by controlled', (done) => {
+    it('by controlled', done => {
       const treeData = [
         {
           title: 'demo1',
@@ -600,7 +600,7 @@ describe('Tree Props', () => {
           loadedKeys: [],
         };
 
-        onLoad = (loadedKeys) => {
+        onLoad = loadedKeys => {
           this.setState({ loadedKeys });
         };
 
@@ -739,7 +739,7 @@ describe('Tree Props', () => {
           this.val = val;
         }
 
-        then = (func) => {
+        then = func => {
           const ret = func(this.val);
           return new FakePromise(ret);
         };

From 4503f318ae57820d5b8ef8d4755ca3a6a5b5657a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 14 Jul 2021 10:07:00 +0800
Subject: [PATCH 196/397] 5.0.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b52f1aae..0991461b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "4.3.0",
+  "version": "5.0.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 99a903cc070a468576e98995f4b640a5659a3374 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Robert=20Wettst=C3=A4dt?= <robert.wett@gmail.com>
Date: Thu, 15 Jul 2021 13:59:02 +0200
Subject: [PATCH 197/397] chore(deps): bump rc-virtual-list from 3.0.1 to 3.2.0
 (#428)

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0991461b..034eb6e0 100644
--- a/package.json
+++ b/package.json
@@ -77,6 +77,6 @@
     "classnames": "2.x",
     "rc-motion": "^2.0.1",
     "rc-util": "^5.0.0",
-    "rc-virtual-list": "^3.0.1"
+    "rc-virtual-list": "^3.2.0"
   }
 }

From 19eeea624f0858bb6625a84d0fdd13bdcaa3e413 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 19 Jul 2021 12:05:13 +0800
Subject: [PATCH 198/397] fix: onScroll should pass to VirtualList (#474)

* fix: onScroll should pass to VirtualList

* test: onScroll should be triggered
---
 src/Tree.tsx       |  3 +++
 tests/Tree.spec.js | 17 +++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 9077d00e..bff62918 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -104,6 +104,7 @@ export interface TreeProps {
   onContextMenu?: React.MouseEventHandler<HTMLDivElement>;
   onClick?: NodeMouseEventHandler;
   onDoubleClick?: NodeMouseEventHandler;
+  onScroll?: React.UIEventHandler<HTMLElement>;
   onExpand?: (
     expandedKeys: Key[],
     info: {
@@ -1296,6 +1297,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       titleRender,
       dropIndicatorRender,
       onContextMenu,
+      onScroll,
       direction,
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
@@ -1376,6 +1378,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
             onListChangeStart={this.onListChangeStart}
             onListChangeEnd={this.onListChangeEnd}
             onContextMenu={onContextMenu}
+            onScroll={onScroll}
             {...this.getTreeNodeRequiredProps()}
             {...domProps}
           />
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 186fd7a7..0eeeb078 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1758,4 +1758,21 @@ describe('Tree Basic', () => {
       wrapper.find('.rc-tree-switcher').getDOMNode().className.includes('rc-tree-switcher_close'),
     ).toBe(true);
   });
+
+  // https://github.com/ant-design/ant-design/issues/31141
+  it('should support onScroll', async () => {
+    const onScroll = jest.fn();
+    const data = [];
+    for (let i = 0; i < 99; i += 1) {
+      data.push({
+        key: i,
+        title: i,
+      });
+    }
+    const wrapper = mount(
+      <Tree itemHeight={10} height={100} treeData={data} onScroll={onScroll} />,
+    );
+    wrapper.find('.rc-tree-list-holder').simulate('scroll');
+    expect(onScroll).toBeCalled();
+  });
 });

From 7767b1b13b4aa0005e277e2af07f8a22b59fac98 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 19 Jul 2021 12:37:26 +0800
Subject: [PATCH 199/397] 5.0.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 034eb6e0..f12d6fe3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.0.0",
+  "version": "5.0.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From d2da30c9ae832c20495fd59c02374cd36449c77a Mon Sep 17 00:00:00 2001
From: Divya Katpally <katdivyareddy@gmail.com>
Date: Thu, 22 Jul 2021 00:05:48 -0400
Subject: [PATCH 200/397] Update Type for NodeDragEventParams (#476)

* Update DragEventParams

* Update DragEvent type usage in Tree
---
 src/Tree.tsx        | 6 +++---
 src/contextTypes.ts | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index bff62918..8907380a 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -442,7 +442,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
    * Better for use mouse move event to refresh drag state.
    * But let's just keep it to avoid event trigger logic change.
    */
-  onNodeDragEnter = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragEnter = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance) => {
     const { expandedKeys, keyEntities, dragChildrenKeys, flattenNodes, indent } = this.state;
     const { onDragEnter, onExpand, allowDrop, direction } = this.props;
     const { pos } = node.props;
@@ -560,7 +560,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeDragOver = (event: React.MouseEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragOver = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance) => {
     const { dragChildrenKeys, flattenNodes, keyEntities, expandedKeys, indent } = this.state;
     const { onDragOver, allowDrop, direction } = this.props;
     const { dragNode } = this;
@@ -674,7 +674,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     this.dragNode = null;
   };
 
-  onNodeDrop = (event: React.MouseEvent<HTMLDivElement>, node, outsideTree: boolean = false) => {
+  onNodeDrop = (event: React.DragEvent<HTMLDivElement>, node, outsideTree: boolean = false) => {
     const {
       dragChildrenKeys,
       dropPosition,
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 14ef86f0..ead1383e 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -18,7 +18,7 @@ export type NodeMouseEventParams<T = HTMLSpanElement> = {
   node: EventDataNode;
 };
 export type NodeDragEventParams<T = HTMLDivElement> = {
-  event: React.MouseEvent<T>;
+  event: React.DragEvent<T>;
   node: EventDataNode;
 };
 
@@ -27,7 +27,7 @@ export type NodeMouseEventHandler<T = HTMLSpanElement> = (
   node: EventDataNode,
 ) => void;
 export type NodeDragEventHandler<T = HTMLDivElement> = (
-  e: React.MouseEvent<T>,
+  e: React.DragEvent<T>,
   node: NodeInstance,
   outsideTree?: boolean,
 ) => void;

From 748b4e400975c852f324908d562a4f3f5908541f Mon Sep 17 00:00:00 2001
From: moonrailgun <moonrailgun@users.noreply.github.com>
Date: Tue, 17 Aug 2021 10:40:06 +0800
Subject: [PATCH 201/397] feat: add dragnode info into tree#allowdrop (#482)

* feat: add dragnode info into tree#allowdrop

* refactor: modify the way to get abstractDragDataNode when allowDrop
---
 README.md          |  3 +--
 src/Tree.tsx       | 16 ++++++++--------
 src/util.tsx       | 23 ++++++++++++++---------
 tests/Tree.spec.js | 32 ++++++++++++++++++++++++++++++++
 4 files changed, 55 insertions(+), 19 deletions(-)

diff --git a/README.md b/README.md
index 1616a90c..f0ca5b4e 100644
--- a/README.md
+++ b/README.md
@@ -86,7 +86,7 @@ see examples
 | onSelect | click the treeNode to fire | function(selectedKeys, e:{selected: bool, selectedNodes, node, event, nativeEvent}) | - |
 | switcherIcon | specific the switcher icon. | ReactNode / (props: TreeNodeAttribute) => ReactNode | - |
 | virtual | Disable virtual scroll when `false` | boolean | - |
-| allowDrop | Whether to allow drop on node | ({ dropNode, dropPosition }) => boolean | - |
+| allowDrop | Whether to allow drop on node | ({ dragNode, dropNode, dropPosition }) => boolean | - |
 | dropIndicatorRender | The indicator to render when dragging | ({ dropPosition, dropLevelOffset, indent: number, prefixCls }) => ReactNode| - |
 | direction | Display direction of the tree, it may affect dragging behavior | `ltr` \| `rtl` | - |
 
@@ -138,4 +138,3 @@ rc-tree is released under the MIT license.
 - [jqTree](https://mbraak.github.io/jqTree/)
 - [jquery.treeselect](https://travistidwell.com/jquery.treeselect.js/)
 - [Angular Multi Select Tree](https://a5hik.github.io/angular-multi-select-tree/)
-
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 8907380a..0dca853e 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -60,7 +60,12 @@ interface CheckInfo {
   halfCheckedKeys?: Key[];
 }
 
-export type AllowDrop = (options: { dropNode: DataNode; dropPosition: -1 | 0 | 1 }) => boolean;
+export interface AllowDropOptions {
+  dragNode: DataNode;
+  dropNode: DataNode;
+  dropPosition: -1 | 0 | 1;
+}
+export type AllowDrop = (options: AllowDropOptions) => boolean;
 
 export interface TreeProps {
   prefixCls: string;
@@ -675,13 +680,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeDrop = (event: React.DragEvent<HTMLDivElement>, node, outsideTree: boolean = false) => {
-    const {
-      dragChildrenKeys,
-      dropPosition,
-      dropTargetKey,
-      dropTargetPos,
-      dropAllowed,
-    } = this.state;
+    const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } =
+      this.state;
 
     if (!dropAllowed) return;
 
diff --git a/src/util.tsx b/src/util.tsx
index ba1458f0..2b8e6c86 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -114,7 +114,7 @@ export function calcDropPosition(
   if (clientY < top + height / 2) {
     // first half, set abstract drop node to previous node
     const nodeIndex = flattenedNodes.findIndex(
-      (flattenedNode) => flattenedNode.data.key === abstractDropNodeEntity.key,
+      flattenedNode => flattenedNode.data.key === abstractDropNodeEntity.key,
     );
     const prevNodeIndex = nodeIndex <= 0 ? 0 : nodeIndex - 1;
     const prevNodeKey = flattenedNodes[prevNodeIndex].data.key;
@@ -141,6 +141,7 @@ export function calcDropPosition(
     }
   }
 
+  const abstractDragDataNode = dragNode.props.data;
   const abstractDropDataNode = abstractDropNodeEntity.node;
   let dropAllowed = true;
   if (
@@ -148,6 +149,7 @@ export function calcDropPosition(
     abstractDropNodeEntity.level === 0 &&
     clientY < top + height / 2 &&
     allowDrop({
+      dragNode: abstractDragDataNode,
       dropNode: abstractDropDataNode,
       dropPosition: -1,
     }) &&
@@ -163,6 +165,7 @@ export function calcDropPosition(
     // only allow drop inside
     if (
       allowDrop({
+        dragNode: abstractDragDataNode,
         dropNode: abstractDropDataNode,
         dropPosition: 0,
       })
@@ -179,6 +182,7 @@ export function calcDropPosition(
       // 2. do not allow drop
       if (
         allowDrop({
+          dragNode: abstractDragDataNode,
           dropNode: abstractDropDataNode,
           dropPosition: 1,
         })
@@ -197,6 +201,7 @@ export function calcDropPosition(
       // 3. do not allow drop
       if (
         allowDrop({
+          dragNode: abstractDragDataNode,
           dropNode: abstractDropDataNode,
           dropPosition: 0,
         })
@@ -204,6 +209,7 @@ export function calcDropPosition(
         dropPosition = 0;
       } else if (
         allowDrop({
+          dragNode: abstractDragDataNode,
           dropNode: abstractDropDataNode,
           dropPosition: 1,
         })
@@ -221,6 +227,7 @@ export function calcDropPosition(
     // 2. do not allow drop
     if (
       allowDrop({
+        dragNode: abstractDragDataNode,
         dropNode: abstractDropDataNode,
         dropPosition: 1,
       })
@@ -271,13 +278,11 @@ export function convertDataToTree(
 
   const { processProps = internalProcessProps } = processor || {};
   const list = Array.isArray(treeData) ? treeData : [treeData];
-  return list.map(
-    ({ children, ...props }): NodeElement => {
-      const childrenNodes = convertDataToTree(children, processor);
+  return list.map(({ children, ...props }): NodeElement => {
+    const childrenNodes = convertDataToTree(children, processor);
 
-      return <TreeNode {...processProps(props)}>{childrenNodes}</TreeNode>;
-    },
-  );
+    return <TreeNode {...processProps(props)}>{childrenNodes}</TreeNode>;
+  });
 }
 
 /**
@@ -334,7 +339,7 @@ export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, Dat
     }
   }
 
-  (keyList || []).forEach((key) => {
+  (keyList || []).forEach(key => {
     conductUp(key);
   });
 
@@ -346,7 +351,7 @@ export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, Dat
  */
 export function getDataAndAria(props: Partial<TreeProps | TreeNodeProps>) {
   const omitProps: Record<string, string> = {};
-  Object.keys(props).forEach((key) => {
+  Object.keys(props).forEach(key => {
     if (key.startsWith('data-') || key.startsWith('aria-')) {
       omitProps[key] = props[key];
     }
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 0eeeb078..9dc0e2d3 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1529,6 +1529,38 @@ describe('Tree Basic', () => {
           expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
           expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
         });
+        it('allowDrop should pass dragNode and dropNode', () => {
+          const onDrop = jest.fn();
+          const allowDrop = jest.fn();
+          const wrapper = mount(
+            <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0" className="dragTarget">
+                <TreeNode key="0-0-0" className="dragTargetChild" />
+              </TreeNode>
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" className="dropTarget" />
+                </TreeNode>
+              </TreeNode>
+              <TreeNode key="0-2" />
+            </Tree>,
+          );
+          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+            clientX: base * 500,
+            clientY: 500,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+            clientX: base * 400,
+            clientY: 600,
+          });
+          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          expect(allowDrop.mock.calls[0][0].dragNode.key).toEqual('0-0');
+          expect(allowDrop.mock.calls[0][0].dropNode.key).toEqual('0-1-0-0');
+        });
       });
     });
   });

From fc1016ec0ae75d92cda51fce8c2d46772868b909 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 17 Aug 2021 10:45:57 +0800
Subject: [PATCH 202/397] 5.0.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f12d6fe3..4921ac93 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.0.1",
+  "version": "5.0.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 26038df5caa73352240fd3d171f0788576a952f4 Mon Sep 17 00:00:00 2001
From: Curly Brackets <76864176+curly210102@users.noreply.github.com>
Date: Sat, 28 Aug 2021 12:45:06 +0800
Subject: [PATCH 203/397] fix: hide indicator outside the droppable area (#477)
 (#478)

* fix: hide indicator outside the droppable area (#477)

* fix: children elements fire parent drag event
---
 src/Tree.tsx | 28 +++++++++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 0dca853e..0685386d 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -283,6 +283,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   dragNode: NodeInstance;
 
+  currentMouseOverDroppableNodeKey = null;
+
   listRef = React.createRef<NodeListRef>();
 
   componentWillUnmount() {
@@ -450,9 +452,14 @@ class Tree extends React.Component<TreeProps, TreeState> {
   onNodeDragEnter = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance) => {
     const { expandedKeys, keyEntities, dragChildrenKeys, flattenNodes, indent } = this.state;
     const { onDragEnter, onExpand, allowDrop, direction } = this.props;
-    const { pos } = node.props;
+    const { pos, eventKey } = node.props;
     const { dragNode } = this;
 
+    // record the key of node which is latest entered, used in dragleave event.
+    if (this.currentMouseOverDroppableNodeKey !== eventKey) {
+      this.currentMouseOverDroppableNodeKey = eventKey;
+    }
+
     const {
       dropPosition,
       dropLevelOffset,
@@ -649,6 +656,24 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   onNodeDragLeave: NodeDragEventHandler = (event, node) => {
+    // if it is outside the droppable area
+    // currentMouseOverDroppableNodeKey will be updated in dragenter event when into another droppable receiver.
+    if (
+      this.currentMouseOverDroppableNodeKey === node.props.eventKey &&
+      !event.currentTarget.contains(event.relatedTarget as Node)
+    ) {
+      this.setState({
+        dropPosition: null,
+        dropLevelOffset: null,
+        dropTargetKey: null,
+        dropContainerKey: null,
+        dropTargetPos: null,
+        dropAllowed: false,
+        dragOverNodeKey: null,
+      });
+      this.currentMouseOverDroppableNodeKey = null;
+    }
+
     const { onDragLeave } = this.props;
 
     if (onDragLeave) {
@@ -738,6 +763,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       });
     }
     this.dragStartMousePosition = null;
+    this.currentMouseOverDroppableNodeKey = null;
   };
 
   onNodeClick: NodeMouseEventHandler = (e, treeNode) => {

From 7b90c11c4aa3458010cbc2031ff48ac4803030e4 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Sat, 28 Aug 2021 12:45:38 +0800
Subject: [PATCH 204/397] 5.0.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4921ac93..9a1614e7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.0.2",
+  "version": "5.0.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From d7cca4c7745d4108a8fcff088fafb93d8caab8b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Fri, 3 Sep 2021 15:37:05 +0800
Subject: [PATCH 205/397] fix: Not do motion when TreeNode is out of scope
 (#485)

* fix: motion not go when hidden

* test: Update test case
---
 package.json             | 2 +-
 src/NodeList.tsx         | 9 +++++++++
 tests/TreeMotion.spec.js | 2 +-
 3 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 9a1614e7..420dc2d5 100644
--- a/package.json
+++ b/package.json
@@ -77,6 +77,6 @@
     "classnames": "2.x",
     "rc-motion": "^2.0.1",
     "rc-util": "^5.0.0",
-    "rc-virtual-list": "^3.2.0"
+    "rc-virtual-list": "^3.4.1"
   }
 }
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index dfb55097..87d426a9 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -310,6 +310,15 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         itemHeight={itemHeight}
         prefixCls={`${prefixCls}-list`}
         ref={listRef}
+        onVisibleChange={(originList, fullList) => {
+          const originSet = new Set(originList);
+          const restList = fullList.filter(item => !originSet.has(item));
+
+          // Motion node is not render. Skip motion
+          if (restList.some(item => itemKey(item) === MOTION_KEY)) {
+            onMotionEnd();
+          }
+        }}
       >
         {(treeNode: FlattenNode) => {
           const {
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.js
index 0bf462d9..78c86fb8 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.js
@@ -11,7 +11,7 @@ describe('Tree Motion', () => {
       motionName: 'bamboo',
     };
     const wrapper = mount(
-      <Tree motion={motion}>
+      <Tree motion={motion} height={10000}>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" />
         </TreeNode>

From 18fc9f8089ed9668f4c9e630601966502fd1e8b2 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 3 Sep 2021 15:37:52 +0800
Subject: [PATCH 206/397] 5.0.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 420dc2d5..ae254e25 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.0.3",
+  "version": "5.0.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 327832b92c716f216f1af687bdc812a6decc3890 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 6 Sep 2021 18:00:57 +0800
Subject: [PATCH 207/397] fix: loadingKey use provided (#486)

---
 src/Tree.tsx | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 0685386d..9618a7dc 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -920,9 +920,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
         const promise = loadData(treeNode);
         promise
           .then(() => {
-            const { loadedKeys: currentLoadedKeys, loadingKeys: currentLoadingKeys } = this.state;
+            const { loadedKeys: currentLoadedKeys } = this.state;
             const newLoadedKeys = arrAdd(currentLoadedKeys, key);
-            const newLoadingKeys = arrDel(currentLoadingKeys, key);
 
             // onLoad should trigger before internal setState to avoid `loadData` trigger twice.
             // https://github.com/ant-design/ant-design/issues/12464
@@ -936,18 +935,16 @@ class Tree extends React.Component<TreeProps, TreeState> {
             this.setUncontrolledState({
               loadedKeys: newLoadedKeys,
             });
-            this.setState({
-              loadingKeys: newLoadingKeys,
-            });
+            this.setState(prevState => ({
+              loadingKeys: arrDel(prevState.loadingKeys, key),
+            }));
 
             resolve();
           })
           .catch(e => {
-            const { loadingKeys: currentLoadingKeys } = this.state;
-            const newLoadingKeys = arrDel(currentLoadingKeys, key);
-            this.setState({
-              loadingKeys: newLoadingKeys,
-            });
+            this.setState(prevState => ({
+              loadingKeys: arrDel(prevState.loadingKeys, key),
+            }));
             reject(e);
           });
 

From cf98e2e1b78a9351999d179d5272793f3f0f7037 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 6 Sep 2021 18:01:33 +0800
Subject: [PATCH 208/397] 5.0.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index ae254e25..d7dcba16 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.0.4",
+  "version": "5.0.5",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 2af7526b010fe0e0ba30d59212ef3367add8bb0c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Sun, 26 Sep 2021 15:00:37 +0800
Subject: [PATCH 209/397] feat: Tree support drag handler (#490)

* refactor: Move code into unique checker

* chore: support handler

* refactor: Move drag out

* chore: Patch more style info

* test: Fix test case

* docs: Update demo

* chore: Coverage
---
 assets/index.less                             |  28 +-
 docs/examples/draggable.jsx                   |  30 +-
 src/Indent.tsx                                |   2 +-
 src/Tree.tsx                                  |  60 +-
 src/TreeNode.tsx                              |  55 +-
 src/contextTypes.ts                           |   2 +
 tests/Tree.spec.js                            | 705 +----------------
 tests/TreeDraggable.spec.js                   | 737 ++++++++++++++++++
 tests/__snapshots__/Tree.spec.js.snap         |  36 +
 .../__snapshots__/TreeNodeProps.spec.js.snap  |  48 ++
 tests/__snapshots__/TreeProps.spec.js.snap    | 102 +++
 11 files changed, 1048 insertions(+), 757 deletions(-)
 create mode 100644 tests/TreeDraggable.spec.js

diff --git a/assets/index.less b/assets/index.less
index 86d6bba0..b449d4b9 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -24,19 +24,23 @@
       -webkit-user-select: none;
       user-select: none;
       /* Required to make elements draggable in old WebKit */
-      -khtml-user-drag: element;
-      -webkit-user-drag: element;
+      // -khtml-user-drag: element;
+      // -webkit-user-drag: element;
     }
-    
+
+    &.dragging {
+      background: rgba(100, 100, 255, 0.1);
+    }
+
     &.drop-container {
       > .draggable::after {
         position: absolute;
-        left: 0;
-        right: 0;
         top: 0;
+        right: 0;
         bottom: 0;
+        left: 0;
         box-shadow: inset 0 0 0 2px red;
-        content: "";
+        content: '';
       }
       & ~ .@{treeNodePrefixCls} {
         border-left: 2px solid chocolate;
@@ -77,7 +81,7 @@
         height: 16px;
         margin-right: 2px;
         line-height: 16px;
-        vertical-align: -.125em;
+        vertical-align: -0.125em;
         background-color: transparent;
         background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAABhCAYAAABRe6o8AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAK0dJREFUeNrsfQl8VNX1/5l9ksm+ELJB2ANECGtYVEAQaZBSFdAW0dpaKbi0WhX9Va1/S/+K2k+1iCztT4sFW6lKkUV2RLZAQHaSQBJCMllJJtvsM2/e75775k3evHkzTCZEAubweczMu/d7ZzLznXPvOff7zsjS7nudhXZaxZd/kKXf//9Cwgkf1xha2QOnS2DzofNw5FwZjM/KgFkTh8Idw/tBz7hImb9xQsV1W9czJf73zTsPek7I5XL3oQCFQkkOBSiV3C2eG/rz9z19Q8Wh7T5+kX3i7c9g6ojekDs6A1796Vg4XVoPe/ILYMnKzbDmxQfZaaMH+pApVFy3Sdupp8cKH6rJ8QQ55pBjvPvcEXJ8To415LDzHbOXH/OAZLK2t/vBbbcFHOOz3LOeMViW5QgYLImwTcrai0MSrdm4H/708ztgwtA0D+6OYb1hysh+kDtuEPxjWx59jUIyhYq7lc2k38HaGk5KtmniR4Au7Z5g34cnZHLF6vTRkyCuzyCAuATurKF+kuFy0aSK4/uXsy5moZuIkkbI94RCplidlZYDvZP7QUx8LD3f1NA46Up1yaRz+qPLSZ+FhIRrvDxgsCTC22DIp1Kp6OORX42GM/ef8sLh9IkeTEwi4fNNyu5Lb7Hf4VW/ZXFaDRV3qxPQcjUfEoaNkWxrLi0CW1MvVhMzOOD74GJci8Nj4lZkzn6UfKAMgLkZdv7+JU/79P95B+IG3gaFm9auNjcZlHKF/EPxGPO2ZC2O0EStmD6aOL4oBixghGpo5EgWr4F+8QOgX69M2Hn889Wkr3LDvefoGPL2kE/syXgcYpRKlQ/5uD7eOFy74fTpj0R8/8kj+sOsCUNofykcThYHLQfhVwW/gi1VW8HG2iVxt7q5GCewLukjLCERmos/g7rjr7PCo/XKVuH6Xa1QqTjyWQwAVytg53tLYfrGWs+x8/+/QNuwD/Z1T9Ve065SoVxx94g5YNY1Q6O9Giz2Vjhy7AA98D6ewzbsg33dUzXnAYMlnzQBFXDn3rsgb8YhihOST0hS3jBwwLVbMM83c/xgWLfrJMydku2DO2g8CJ/b/gNmpQmWXXgL7HY7zB/8sA+us2zTgXNs3oVyv+3jhvSC2XdkyTp7HMZpB5axSy/ww7SQkDXc53ztqUMQ2XsmvW93Mov6jL2TEKwFoPEqrl4o6ahtfBXgvj9yjze+RumSkj0RLh/bt4g88CzqnXbXotv65IBN2wqt5gYyAsfvv489QG//2vo091zkn1wrhyEpo+Hk5SN0DCXvpYIhny8BORx9o7ZPhO9+fNyLfBfmnffBYdSKgUMwz4fR7ZN/2SiJW1exDkyEfGazGaw2B7x77B1YMPQRH1xnGZLmzYW5wBAPxDid4CREcNht4HTYyJfBBn/dWoTE6fRxGKcNXE5ru147YgQBxEOxaX0AWuoAHBbvjg7BuNhG+mDfsvxvHhISUE7G6BmXDk3WBrC5rFBUUsA1uOObMwWn6O2gfoOBdTYA9pWX5T3kIWCw5BMTkMfx5o98QhySA6NWDByu9XzHCrgUixTugfg58PaFZWAlH1JLcxP8aeybkrjONCFpdBHRUF9bQUnjsFlDHkdIvmDGwb7tJSBiPF5SIR+lJMsmV10Tmc+d4FmX4fSOz//PpwUkdIIyNoVihOPJlLJRKo0SjOYWcAHj8Xy88Y+XVj4KDnBCTFgSxXieK1jyyWRiAnI49HxCE5NPiMN83Z6TZUE935bDBbS/FG5G2gz4bf9nQW5Uwp9y3oR5Q+dJ4jqVgALS0CnGTRr+cSjjCMkXzDg8AdtzCAlIUwYOO9isZrBZuIM3vL/7yw30wPsO0sdlsZIp3+UQvw4H+RtsNguZjSx+Xyu22YgntVvtmINxeAgYLPmE+R5vnJxGu/7IJ8RhsnjH8WI4fF4f8Pn2nSyBTQfP0v5SOJ1KR9d8Zx87A49lPwaR2khJ3LXsxIkTbDC3kh++2/PFxPWgj1PS+0Pv/lmUQP7Gv9Y4CUnp7RoHp1PWaWnXIZyCzXbnebPJRDwXruUs9Ghb21k8gQhtw6ibLHksjOuiF/ksDDcGGcRKyP180Wx68MY/ttIvCxmDkpkbQ8l7svaSTwp3LfKhYWoEk8WYr0M8Rq1S5Fu34wQmlT07G6HirmWjRo2SBXMrZeih+GkXSVN84QS9L/Qw7R2H93zBjtPRKbimyby5qUafHR0RAbbmBuKZXBDJr9f37IHpT7m9IQnytDER0FyjpxivXGSdeXN9Y022JloHLfYmEoK4vJ7Pbuden4z4uxhNItQ311CMIA3TfvJ1BIdJ4p/njoOn3v8KXl6zHb49fZm4Zgb2nyqF332wGX617DOYP30UiJPJoeKC8YChmHitxpOmvVOweNptzzh8ENKeQ+gBF28oWllfkA9MeAKARgcOhwOq3+QiZD4arn5rFm3DPtgXMcLXsPP3ZSsvNpyCSCYW1BBGXreDEnbhiSn0wPt4DtuwD/ZFjMcDirfJgrVQcTyZMFmM+TpMmWDUyu/pLnl4ql8PFiruWh4wFBOS5sKpwx7S4JRK5oeQxhGSL5hxAqVhAmF4I7Fvw5kKwxvKo7teSx07BViVHhxNdaBfeg/nZNThoIojgUd8GuiP7gLsixivARuhofZC0xunlAdfy0qZAA2qKmiy14PdxX0x1XItxKgTIF6RAqcqDwL2RQz1irgf90M29IChkLCr5AHL85ezVy9tbtdrTxwwC3qNeVrG7wWP+CA/YtXMjFfG9UtaEjcgGzTRsWR9L6M5QScjA1uTAQyXTkFeSe2yX28tW3ryqTFGib3giIlLU19JHxW/pG/MUNBpogFUMpoTlDtkYLQ1QWnTeag40bDs0CuVS0l/I3JPdqPUMOvX/VM+NfcnDHqyLahqOV8G44dmwL1uVcuebf/VzH94geRXu1sNc33FCISA+J7pyNH3rbtSnxmSHD0pPVbXH9v1jabS89XN+17aW/lX8rAUl3yEgKwEAT1jjHqxxzOJAyInRaeG0zFaqsyldRdb9514u84zBqdFcIsRKj4mEQtDoh+nkYTkLWRVTBaSZDEJDIbcVu7Wie1W6LMsvY1QIeLQkjJzmAm/fg9mj4qCR0Yp4cP7tJB36TJsPnAJlqxUYCBhc/9RPkIG3OtF3KMEt9IXx7Z3DdiRabirjtMeQ0KhRyJELCREexGgkrgvsmBzbzfjtjK2k36B5no6BjkKCdHIGHWSY4BAUdMmRgiSRCwjyvGEiEMSrd+8Hf72eDrcNZDx4Cb3t8HkPlaYOYiBf372Een5Cx81TCi4zloDduVxgjWhJ2OXU3IY3EfQJlrGtWsMjoBuEpU7h4NcoQBFhO/OSNi5J8mHLfoC+MEJBQlF/cd74XhVC08i3AVwhg8CB/HWytbzoGw+CVMyagih5ZJqmPbiuj1gYBu7+pTwYdB6wGMLs6/LGEouE855MEoif3o+JJHLLsqgczgF7auk/cRqGDEO1244ffIkssTdBaxMxeXDokeBMzILNKUrYHLvavjxAC3tj6ICMa46YjocMebBuuLf0W25GelPQmzJmz64W90DXk89oEIuWz0pMx0GpcVBAiflg/pGmFSkN0zaX1ixnHGxAfWAoYzB7ZG5p8+AOkCXRLjvxqEaRkqKxW0oeuMwcLh3mJLinJpUD/k8pJZrwBk1nOJy+1+l/aVwSD6hGuar0q8kcZ2ZB+wK46AeMC5rhOThtKAesOCa47lY1+KYcO3qp340HIYMjAMj+Ug++FpPj3/n6ek5bMM+2DfYMYqauQPv+xuDEpBfSwXaE6YkEm0B8jiaLtg+0Yd8uDMixmHUOq4Xt0Z0cEGSb54qbhzF5SQ30P5SOFTDNBgMYBKoYaRwt7oHvB56QJVCseLROzPBwJDAshVgywE97PhpmudYv1dP27AP9gWRHtDfGLjli0czCQH8jcF5QHfgEFAHiCQS70HzAYfbpNQwYhymTPIuWbjna5X2Uor6AxRzVB/hpYYR4nDaramsgbraq9DS3AjPjXxeEnere0A+ES118HpA8WGsPtSGd9gXTRyQAmQxBVctHGGQdGivFXJ98DG2YR/sixiv1yAaw+bkMHZCODwOHNf7HYPzgO6oNaAOkBLJ6e0B3bhAahgxDvN1m884KQ4DB5nL5kNqxdVvKW5rcaKXGkaIk1LDSOFudQ/Y0a041AP26RELda0oEkDFimB6t3jfxz7YFzHC1yAeg8fh7dGTeg+hpcZQejyZ0xJwb9eFbp11+npAiuPUMMO+zPYRJIhxmCzGfB2mTDBqxYAD1244faIHQxLJLJXwTVkMbC5Ng5cFahghDgOO+QT30Nz/criTT0nibtWdEJvhNGurPwnhkYnQUnIlqNesigwDTVyUlxhBrlCOUqmV0NTgAifrHRpYbS54Ok+Q9CDeMSVeSTHCcf2NgXiefPx44jG4KNidr/OkWvjAgXgTFz3cJHIx3h5QhCvqfRuwh+8PiONVLTRf55DTqFVlugJK/eee6RpJtP5CmqQapr24zvJcN1oRba49CpFpCaAMTw76NTdePAtys9FHD2gnrDET19dGHi5/jOf01dy2b1pyPApRyRStAhewPnpAqTHM1J2Gtb1m8lg8hjsP6E4Wi8jHT58eErGMKA8YGo5LEv+C5vUwZYJRa06yhazdouj0iR4MSSSlhgkF11l5txupiNbE4VruIET16hv086giI8FqqPaagp1W83kSyGWjgspi95ZRWchijvdgP9vRCpFqOSGRE1xWy0VvGkiPgXjEfXpPpOexeAxKQPE2WbAWKo4nk0fVcug8PLnDvad7z1A6fYo92Pp1//QsOXjcFwT3wrdlkNMvA+524/Zs+69sfeFR2nH+wws6de12IxXR2oRsuFq4jkS6MSDzc722DwHDldBQ0uClhjEbajbr65uyI8KiocFI1pPUg3GEaTA0e+7ja4oI14K+vplivLyxaAzOIj2C2jmbbfD5rATJMbrVMG4PeK1bMe7l1dvYVx++nXo+saE065O8RpxaO3Wc2nMfs3IohoiE+KD/XkO5Hpqq9TB09gZOQRCelJzz3s6q2dkZUFjvAIPFQZXNW+e2Te2zvqiGuDAVZCaoYNOpMjj62+kprLm22uMR/IzhtU4k3xGpMZShqlpCxQk8GUzN/Qn1ZLuJJ8srcXuyNjUMCuFcUp7seqphbmZFdFTanVB+dA9oI4LXHmJfhhEs4Sx1DYaSM2/sUitfmzIwFfRyFupMDrjnX3raHE6mzBSdCtKilLDrgh6wL2K852rpMczu6RjH6OFnDDoFv56bLIypgf6TiQ65jEqqX95Y6ukaCKeOwTwj4sgU0+LywqElZeawuc9+AFNHpMKUoT3gsbv7gr7GCPlnC2DZ2m3w1lNzmNrCozLxFIy4F5d/QXG5BLfYF8fyuGCm4I6sAW+0Ijospp+MYXTspbz89kgHIDJxmOfRmFUn7fm/HvGO4+lVGrN93JLstDjIjNeQz1AJODnKwAkGsxW2nqsiHjdvWdnyX7+DGOGIHRnDqzbMtcgn8/cxSZAvPae3uw2g6pjeh3z/+no/vPDj4dAzVkXCczvU110FnUoBM4cnw9j+PeCLvXnwwF3jWCEJQ8V11hqwKyiih+Suvh75RxMhxdIygE/1j731THTGkEm6pHS6TWWq05c2Xz6/r/Ljl4Ravus2hrJd5JNgoCZBS75UMircczQ5vMj36O5HYe3da0mzzGvanfncB/D8rOEQHyGDxsYm8qY7qKQHnw8vNI8k0drdWanw6qovYOPbT+FULxPjHLEuiEiKapsFagjOyvrgOssDYn4OUyTSpqDt3+c4HTHijaiWj3ixQkKSFysBJLV8Ys93PcZQtod8MtHnieTrPTrD4+kqjldA+pheHvJ5uC1YLdIaL9mpkBSrhEZDE9iIFxMGQi6yesUjITERZowaQPoXwdwpo71wzhgWwpLCodqip3vCuC3Xt2d/MLMmiG2ReeE6ywNicjiYPN/3NU6oJpRVwUI2JD1gR8ZQctwJjnw+V7mx3ONH9/4c1k5dK0k+fnze9pDAYfKQHmCxWD2ez2tI8hivzDKZTDAsIx6253FEEuKiMmMp+YRqmGf7PweZyUOgubrJC9eZa8CuMM6Kb1rZ1ro6v+0NBRfg97+5A2JjY2X8+yvaRvPcb29tP946rAcMmnyit8VzJQCSbg+Zbqet9SIfTr+0XYDLLy2DBVMzoIG8aYFSQE5CwrSkCDhbWuWDQ5OqDfP32R/74G71vWAXw8BL8/p5Zg7+YBgXVDZY4W8F5L3aVUGWOo0sT0IpC6W2n4S1Ww/oS8AA5JP5MNCbXVLkqz5WBS5TW1JoTL8MqK4zgVbOXTfsj4TYVtXQCtkDUnxwaFK1YaRwt7oHZJ3cLCKswcPSrTG8pJJ7/C2TCsyWYkpCqXWxuLbfpu3rvNrDlTEwe8KjPrX9vL4IrGtxnC58xaNTMoFRkQWfg3jfZvdSza0HvK1PHKzdV7jaYDIr5TJ5W33AoMknmoJl7j8HPZ/QfMgnDEImZMLpigbQasNAofC9eJ1/LVqtFs5fMcAUsp4T48zVRugb399LDTMkfSgYq4w+uFveAzq8lzE8+Rhyh+G2NaB30SHQl1RDQUGBlOfzqe23fsZJr+Nv0/ZJ1vYTTrsd0gMGSz7xO+NscYKeBB6UhHev9Us+IW5CVj/49lwVNFoZCA/XuasoeC8BwsLCwOiUwb4z5TBh2EAfnKOKrBEJ2XDN99Hsj2BIGkc+W4XFBxeMx7leOyo3YhzGYfd4PtThIflMxPsYyREbEwY/e2AW3Dt5FrBkWm5ubvZd6thdi7BeH1/bz2Zryz1iXT/+oG2kD/ZFjOg1SOoBUQfIawID6gFDIR+PY5oZT57vWuRD+2bHZuWrj98Dh4uugkWmhuiYGEo4lPNrNBqIjo4mLjwMjpc2wgsL7sb+Gikce5WF+rw6qDlYBXWHa4CtZSRxt7wHtNuJp+M+dCQeHrwipcUKEElWIj2HAiWglAlr+1mxhouzLe949NBBepw8eoq2YR9a2y9IPSCSDvWAQn2gWA/IETAE8glxTiOSsJISLxD5+C9MbeFJ5cw7RsCqbefhVIURXJoI6NkzBeThUXCuygJ/21EAU8ZkwdXiUzpB1BQq7tb2gMRjoYdxuPmF5LM6uIO2IzldeCtNQGFtP5uVrKfNjZ42fgr+eNoB2oZ9VGEqT20/D4l5PSD53FHzhwdvSEL+Md5iH7VapAcUb5MFa6HiKJkunVKsX/oErYzwlagywj8emEErI0iQKFTcLesBGeKZcL2HJOTJR3dX3Ao4/OydDHftiN+9aHdtPzKHgEKw8/KH0p+K3CVXZpev7ee1m+NHU4jG6wIl9YDiH48J1kLF8Tb/4QX4tZDhpZNSl0/iPq5QuCDY170m7vuIXrtMjWi7DcxubonJh+f5c5iukSQfV9svG99UK+O992xymL0ehynCweJsq+3nWUcG0BSiHtCzWyWlB/y+1TACcgVVG0ZIQt46Qw3TXusqNaJd7qAhEPnwnMspTcBAtf2qL7d9MRJSe/rU9vN4OD96wDmb6wW9IiX1gJ1WG6YRVPju4CIFoi01XjgkFdaGmbiIqw2zYKQSls8Og2MlZbDtYDG8vEoBq16YZyP9JNUwC9/hasM8QnAf+OK+NzVMV6gR7SJRsMPpSz7P1Mhw60B/UzDW6Yv7NOrVcRHToRkMYMTPT7AG5O2Fs/fT2n55DTu52n6COLjo3cUrY9J2vjo7OwLqyQyOesCZ/6n2eh5eU5igYWBTQT3FwBsPdE5tGCTfhejxnu2SwZX/8YIhiT7dvB1W/yId7uzHgNPWQr6hdsjp7YTx6VaYMdAJ6zd8DPPnPeajhgkF11lrt65QI5rBKJj1Jh8SzsG0BSH2AASUqu23+PjdPrX9eir7+NT2a5tbO6gH5En08fZGdy4u1ic5/WC/7ZK1YertRtiebyZ91ISDsZJqGJngumBUtdxOPN8qQqLbCYlMNgYssj5gDUsBhaUMtLaLMDa1hoZ1i9/dAPtXPONRwwhxlxSJYIhty/XFGKsI7oAPLlgP2F5FNP3z3Z6PtxROfUSlWf7GD2Yc3oIZx2FqhQ/eWndNomKR8fDwcKkm+77flb8zcSmjsY7aTWv7pWnI36EV1PYzN8Hxpt18bb93xEFeh/WAvAcLuCcsURsGyVcA8dB7THxANYy4NsyPyfR5ByGRmZCvUT0STGYH2IzkGyfrCVpCxNjmrwmZ9DBrQAMcPIM1XkZ44YqRfJpYbzVMfH/yLR8PYx07vXDBesCbtUb0b56aAiUlJVS8Ech0ul7Qr5/fS1VNXNHIyk9HvVgTTG0/yTFC1wO6p08pz+fRAUrVhmGMAIr4a6phQCABx4AD13wMmT7R8yH5mpqN5A20YIKTvFFhoFT2B5WtEu7ua4B/H75AiSTEoefzp4ax62VeuM60rlAjOjU1VUaOjv4pIdX2E3nB0PWA/Not0J6wVG0YcBg9ktaAahhhbRgS7WLAgWs3nHbR85lNVjAaLfT58LnDY3uDkyxsRiY1wbO7rvjg0PyqYUS4zrSuoIjuMPM6UNuPtw7rAfmAI+CesFRtGDq1BlbDDLn0IURaUBqVSc9jqgWjVgwccM2H067MrXPgvwBy02V6XfF31ToYN7S3Dw7NnxpGjOss6yqK6GXLlmE8mivVRqbce+fMmRNwHdw16gO6o92AOkCJ2jAyTFy61TD+pFg52iovHOb5MGWCUSsGHGHEC+K0yz03mYJJqB5mLCQvzAK7SlMgd+oQHxwGHLwa5u1j73JqmLShENZQ5oPrLOtCiujcJUuW3CvV8Pnnn+PBXouEbruB9QHdqZaAe8IStWFi7FdhcP3OwGoYidowm88r4FCxEzTOGoghAUecvIK82HBIVNdAgnEnRDDlcKJSA9suJ8PtgtowPC697gBENZd7qWHCGy5DSvkWH9wP3Qj5KAkD5hJDrO13Pcbwqg3jSbUEKrMhXD8QXIyzkeb5ClLnek271POpfXFYuWDl8/NYzNexDhfkkGgXAw5HK0vTNUqwwokqDXxe2AP++uwc2Pv1JjkmlH1wJNrFgMPBBMZ1WxsJ/XhCLy0fKmj4ZSHKqe4YnUbPRak4Ld8HO0+vIF7s76KAJOQx5O7NvA7Vhom2VMOQK/+AIaV/a1vzBcBhknj+vJ/D01tS4I974+A7PQtKVxOcqSZrmkMp8Ny+LHjoocVQV3RM4Y7QOoT7IZt7Gubv+7wnUvUBSUxHD17Th+faWx9QWBcQ7+M5qTE6qTZM5jWxtYXHZJgsxnwdpkwwas0hgcNMsnZ7nkyfxIN5KiOIcd9++Bu6F7zx0HlYwteGmTYUXhBVVOj2fHPEAcsWcR8vLR8h3ZlCwTXcQ7gKqVglYVhmGtQ5OS3fN7Iyr98LFo+BhuMI6wLyJh7je1fDDByQDGNypnleO+bqpPJ1/PSZf3Q3SOzrXjc1zK1ieCESf3kDf421MNVyZdNKmGTYf2/ekv3oBVeOW7aNrsPEtf2E9fx4w3NP57naVR9QXBfQM2mK6wOSD7jdUxUhkCxUnJBUST0zWLO5FaxWE819KVUa0Gp1EB4eCbU1ZV4E5zHtwQmI/oMgoERejz4u/2oV1Odvh3ELngWXTAHHPnkXpz9PIOCt5QuTHF9Ky+eVQLymHtAddEjVB4xLaGNrW3VT6Z9sKCpoK8cbKi6t1+AjrS0N45qb60Gni4aIyDhXz56p8pqaSpfdZpbj+eiYHmxkVHyevrxgfEdxPyQC8rf8FYdIPsOJnTDup08CU1cGNWabaBnvreUT6vf4un78ufbUBxTXBeRNsj5gsCSS+6lDJ4XjZgDWc8mg0JBEKEGKjU12pqX3VvLpoLS03vRWX1HubG2tV2K/64H7oRAQ32uGYTzk029ZA00nd3PkM1RBpcEAVfn7odFsX+/xTpL1AT10gfu/4jR9cvJ5tq8+oHddQN4k9YDBko/+XkgQ5JOTV4uPS4vPwMDMkV44nD7RUwlI5GNp6b2Uej04Gw1VSuyPX+hQcZ31gXcVRTQ/zSLxuAvSuduaHR9By6m9PuSrbDJ/OWfN/oXscg4rpeXjLx/hNX18bT+xlo+3joyhbA/5xJ6M/n4I66KOCL91YvJxfbxxuHbD6dMfiTxkSuultNtMtL8UDn+awWhsBZOphawDLZCQmAKJPVJ9cJ1lXUURzXs/JB6WNMHLKivOvwEG6wbodddMYFobPOQrtmlrFqz5+hEQKlo6oOW7HmMICHht8kkTUAZ1NWVkfTbIh3xCcnsiIhI44NrNswsTwNSacFdLS4NcCmc0tpB2Hfmg7GCzGqG6uowSUIzrTOsKimg0/Kzw0la1Wk01f6f1G+BHD34KX3/2M7BEtYIzn4SefUZDSa3iJMBGLzlVl6gPGCz5fAnYNrXqy4ugb/9hXuQbkpXjg8M3FwOHYN5YGmBUFUvizKZW8o13ksNKK34K1xlCXKcSsAsooo1G4zfLli3zOjesB9C94WG3vwJnDi6FBtvkGiSf0+nc42eYG1sfMFjyiQmIOOGGgxT5VCq1Fw5TJhi18oFDIMN+pL9cCofEsxDPh+TDD0qjDZPEdaZ1BUX00qVLscwFBhVa/tyHr2udxPv9BO9fLrdtfvL9jS8Rz4fyqCbJ9NiNrg8YLPlkMrmP68do15/n48knxGG+DlMmwXzA2A/7S+ESEpPptMuTLzk5QxLXmXajFNEFTw6HwStO8wEIztM1oiHvEz5Y/Afp5z2/Vw7rhqqAcdkBLxmxbwU7+TyRqK3k7RtLlz4muIQvEadStXYEoM9RyNUE64Chd3FrvA7rAYMln7iQEI/DKAyj3YuF30mST4jDZDFGs5gywajV3wur1Jc7TaZmZXR0giQO13v8mi8QrlM94A1URCMJ3Qk/uvMvV2t/YW+8mnbbP0rfEPa7+MLtH9gbagsUYeErhOd5AnMsBvJ5AUdCGyaLFSN1UWn/pgQ06uc4GeaoWsP1kSqw0GE9YCjkE+OQhNciH93LrSmTYbIY83WYMsGoVYpELS31So0mnPbv1bt/yLjOtBuliHZzjouA7fZ0xmb+feyI4Y9oe6SEnX2sX8/bPi6huxyXXph4OPXBpwdXf7k6xlJdEaEM1y0L+EJYemjkSuXc2KQH6be7se79ueBkTpHzwXyrQqsPGAr5OoLDnQpMFmO+DlMmGLUKdzTQgyGJsF9zU12HcZ1hN1IRjcliBXlvXYSFrItZGNM/a2Hi8DGgTeoFFV+tXXRyflqkKkx3T8qMuYm6qHDIePAJKP/io7dMZRcjlZExr0jnEnFGkxHis1qNWjU9PDqHfnh432Gz/ZG02QIVFA21PiAloHCbrD0WKo7fJuP3dDFlglErBg64dsPpEz2YmESh4jrDbqQimpbZUCh0MmCfiUzNeDx13F2gwKXglTOQPu0nwNrMD0cNGgYxWSPJlEPen6gEyJj3K6jY8eXvLZeLFCzretntSbWEwoPJbSznT1gzmbz6RsUPSpYrjPS58L7NdmIWacPoNZzyHthGcovFBvk8kaQekNcCYid/esAf/C8l3Yz2wOA42Su3J8+K0Cg39X7gCVBXFQJgVSvCHohPRdZw921mEj6Ygf5YS+YYEpemwvkX5trlSnU6WQPWnd8jGx4eHb9RE5auZom3ZZytjFyh08T0mJyg1XG/fmM1GZmmum/qXYzJplBGKmTAgM1SYTc3N9w3dCpLF5KjPjj2mylZfd7r1ycRqgXSqzcygUq5cka0aQaSSVxccvkq7Dt3+bcnnhr7vrL747z57MvCRjA5mJo19/YFFaafYhKANRroJRXQWEtIZ+MWdCzNygPoIsBRrYeGvV8DYzbukkfFUXLlnwDn+Amy2KSMB2M0ukHEtVUC66zFbAkwjhLOtWl7KHr0mpkkUyaBXJYKNlMRVBT+uQmxQ6fya1JfPSBvQj0hmlgPKO/+OG9KY3eUtJx5YsvlJaUbPoRWQyPIIuOAddi5MNWMhQYc3E44kjAsBhrPnYKGA9s+VIZHPk/O0A3al96G4l07DM8e27M8z1C9lZWzRmCZCkK+88Qb1nEHuY/nsA37YF/EINYTC0jUB5SqEei3PmC33XxGok3rjpLmtxd/flb2bmvrW7fNnAtMSyOZSO14Fbe7Lje5lWPiTg21B7aBXKVaK1NpCoHlyFHbAPZn33T9KzG2quS3j3yy5LHHh98TlTxM6cLC5wy3ly5TRIJcowBD+RfOj/9+esd7nziWXW2EY07G+yJ1Xz0ggJQmUKwH7PaAN6E9MTIRsnvqIE6riOyXGJGYkZWNmjwy81ro3jhrxws7rJz8GNeBhJg9J9xDSMVsIeQTRjwsIZKtzgAHNu93vH7hfGmpSmEFp9PEJafJgffxHLZhH+yLGBBsgbn1gNT7ovaPP3hDbaDnnNNJyGiR1gN2281hU3pHwsS0yORkjfPtuyeOfJiJiQVTTSklm8tBQk2tjn6wMpZEBFgvtr4cEsdMhLDBoxIr/vXXveTMIEzx4Vg5I8iDPgC/ewI00Yk6tdFE/KcslkyTHL/sWJyInMvoq1Ov+JNB8+c1AEWXAY62VW7zqwf0rRHoqwfs9oA3oT2+pQylvrGT+8U9DGNng8liAauhhu6L4+/yyXQxQEILLlmNsjRTE0BFAYQlpQKZXhPJWbp39uv5AB+9A/Dko6B2srrJkfFjeqq1yYQkPaCp+rITD7yP57AN+2BfxCDWk457d/HK/LJ6qvXTkfDGZneAxcrVCMRbPPActmEf7Ev1gN0EvDnN5HDBL7eU1fzv2eZv2ILDINfFgiw8FhjycWrTB4PVwQJTdRlkvQbT9R/EJ4NLGwtV/1lpIfTED/4cjvPWyyRAJsu0pARI6ZEYkasN76O1m2ohf//emvf/XLIWD7yP57AN+2BfxLz1suAF8XrAC3roH6MkHZSglrNktmXogffxHLZJ1wfstg7ZjVBHMy62edHWy4vMrV+uXJw7drI2dSCZL00gNzZB6cmjrrPl9ed+Fh45TJZ1OzhbGqDuzHFoLS9ZJVMqn+PHK6twLwQB1Ep1i9pS/N+WndsNez78pPGTcAUcxLYt31ZtWfzIlkemz4ibarO0qMmyUo0voIkE2sOHcvjr93vB3RaS3SB1NF7tf+l33zb80gbfLX8uF3Ihawprzd9y4Zktxa8eqbaesjI7P1sgU4ypb7VC/ZkjW+UqzUrcv+ft/oWeu2VapeWxIRklg04WwemSSii+8zau4fhZ+O9f/rfx3DcHG4dfKIMiqxPKeFCJdwGyDv5ecLd1yG6QOhpJeOV/vq193Ow4/qdfGh2x4S31G/brLRvpWnFH9cNNlk1v3De6f6E6Ivpt4pLMwp2v0jZni97oXEEpFJJWGr7mFbY9CRKytBLK+DYp69jvBXdbxwl4g9TRhFCMO7H8C885T80CwFTHQ/6ea/HixfQXqpzkOd3XlTjdAhKVUqmkekDSdgyoHpB1cuonOZXh4fUnvHW8PmC3ddiCUUeHMg5vwnE6Y/+e13XixU3k/sjExESqB6ypqZlDzh3Fdr7P9bRuAl4nC0Yd3d5x/KmjPUHJx4X+hkGpE1Y/wIjXq5xa3mPXrNujIUSbO3r0aKoH/Prrr+cSAqLi1NYZ71t3GuZ6ecAuUC9aYIs+4Yi2yE3Ga5qggIBWrVZPz8jIkOGB9/EcLzruJmAXtcDq6NDG8VVHS3o6VuKAQjPAH+cHJiFZ72kJqbAy1F3kmEYeTyDeb1ZqamoyrvHwwPt4DtuwD/ZFDGK7p+AuYjdQHb3ovQWZoBddKGkm8UGJOwR4dV4m/HFDIV/Pb7HI6w0KDw//Ii4uTo3Bh9VqZTTEBg4cGNvQwF17jvdJgPKujZhWq1WgFzQYDPaWlha88Ol0NwG7gN1IdXQx4cmFAPGmiawIXpydCW9v8iVhZWWlMyIiIpas92KSkpLoD1objUbiee3AE1Cn0ymys7OTSD/6W861tbWwffv2JsR2e8BuAzMhWKvZfzsVVRGP+JcHM+HZzwq9yrLt3r27mEyzz5rN5oUTJkzIwd8cQRIS7+ZZ7yEho6Ki6I+Jnz59mj18+PDR0tLS1fv37y/uJmC3gYXEJiYz47ddp1ZAShgg+cBhbvmHl3c0mezEm/2LTMMlly5dWjJjxox7evXqpcRUjM39K5xIPAxAvvvuOyfpu+PQoUPLCGGPkWnZ3k3AboM0HSFhtPelm612BqpbuURxZqIC1uwrhNbK0i8vvDrzKXjSK5JlCZFshIgHCgoKLH379h2QlpY2kKwFaXKaj44xSX3x4sVS0ud10vf49YyGuwl4E5u16er6d3bCfKm2H93WDyI0cvjnEQ/5Hsn5qMCnrgv+zFdCQgKMHz9ek5iYqMbIlwQbwO8Z81W3sC03N1dz5MgRqK+vx/VjNwF/6Hb6uTtRTvAazrTC84RoZ7J7quDNXYHJR4IPGDt2LAYdaqVSOblPnz49MdDA7bmioiLqAgcNGqTEilvYRqLfyWPGjMlXq9X2Y8eOdRPwh25uUpVKecY3d8H8QORDmzZtGqZesKxbSmRkZC7xcloMQI4ePVqTn5+/FfsQbzczJyenJ7bFxsbmtra2YiGkMsR2E7DbAnlG1P2Z/JEPrampiV/nqck6T028Wsu5c+f2HDhw4BPiBakekKz9tpSXlz+SlZU1lUTIahKc8DnD6/Jauy9M/wFbXFwcfxen4IHEyw2qrq4+3djYWNy7N/djj1euXAHi+fonJycPv3r1ahEJTlBhQyNgMiV3E7DbOvDh+9buwRmRrv2EQYi4zRNCXwfudBOw226o/Z8AAwBphnYirXZBiwAAAABJRU5ErkJggg==');
         background-repeat: no-repeat;
@@ -218,11 +222,17 @@
   }
   &-indent {
     display: inline-block;
-    vertical-align: bottom;
     height: 0;
+    vertical-align: bottom;
   }
   &-indent-unit {
-    width: 16px;
     display: inline-block;
+    width: 16px;
+  }
+
+  &-draggable-icon {
+    display: inline-flex;
+    justify-content: center;
+    width: 16px;
   }
 }
diff --git a/docs/examples/draggable.jsx b/docs/examples/draggable.jsx
index af611061..f52142cc 100644
--- a/docs/examples/draggable.jsx
+++ b/docs/examples/draggable.jsx
@@ -1,15 +1,32 @@
 /* eslint-disable no-console, react/no-access-state-in-setstate */
 import React from 'react';
-import { gData } from './utils/dataUtil';
+import { generateData } from './utils/dataUtil';
 import './draggable.less';
 import '../../assets/index.less';
-import Tree from 'rc-tree';
+import Tree from '../../src';
+
+const gData = generateData(2, 2, 2);
 
 class Demo extends React.Component {
   state = {
     gData,
     autoExpandParent: true,
-    expandedKeys: ['0-0-key', '0-0-0-key', '0-0-0-0-key'],
+    expandedKeys: [
+      '0-0-key',
+      '0-0-0-key',
+      '0-0-0-0-key',
+      '0-0-0-1-key',
+      '0-0-1-key',
+      '0-0-1-0-key',
+      '0-0-1-1-key',
+      '0-1-key',
+      '0-1-0-key',
+      '0-1-0-0-key',
+      '0-1-0-1-key',
+      '0-1-1-key',
+      '0-1-1-0-key',
+      '0-1-1-1-key',
+    ],
   };
 
   onDragStart = info => {
@@ -88,16 +105,21 @@ class Demo extends React.Component {
       <div className="draggable-demo">
         <h2>draggable</h2>
         <p>drag a node into another node</p>
-        <div className="draggable-container">
+        <div style={{ border: '1px solid red' }}>
           <Tree
             expandedKeys={this.state.expandedKeys}
             onExpand={this.onExpand}
             autoExpandParent={this.state.autoExpandParent}
             draggable
+            draggableIcon="↕️"
             onDragStart={this.onDragStart}
             onDragEnter={this.onDragEnter}
             onDrop={this.onDrop}
             treeData={this.state.gData}
+            // Virtual
+            height={200}
+            itemHeight={20}
+            virtual={false}
           />
         </div>
       </div>
diff --git a/src/Indent.tsx b/src/Indent.tsx
index dc10caf8..93808c88 100644
--- a/src/Indent.tsx
+++ b/src/Indent.tsx
@@ -30,4 +30,4 @@ const Indent = ({ prefixCls, level, isStart, isEnd }: IndentProps) => {
   );
 };
 
-export default Indent;
+export default React.memo(Indent);
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 9618a7dc..59f65247 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -85,6 +85,7 @@ export interface TreeProps {
   checkable?: boolean | React.ReactNode;
   checkStrictly?: boolean;
   draggable?: ((node: DataNode) => boolean) | boolean;
+  draggableIcon?: React.ReactNode;
   defaultExpandParent?: boolean;
   autoExpandParent?: boolean;
   defaultExpandAll?: boolean;
@@ -184,7 +185,7 @@ interface TreeState {
   loadingKeys: Key[];
   expandedKeys: Key[];
 
-  dragging: boolean;
+  draggingNodeKey: React.Key;
   dragChildrenKeys: Key[];
 
   // for details see comment in Tree.state
@@ -249,7 +250,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     loadingKeys: [],
     expandedKeys: [],
 
-    dragging: false,
+    draggingNodeKey: null,
     dragChildrenKeys: [],
 
     // dropTargetKey is the key of abstract-drop-node
@@ -428,7 +429,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     const newExpandedKeys = arrDel(expandedKeys, eventKey);
 
     this.setState({
-      dragging: true,
+      draggingNodeKey: eventKey,
       dragChildrenKeys: getDragChildrenKeys(eventKey, keyEntities),
       indent: this.listRef.current.getIndentWidth(),
     });
@@ -515,7 +516,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       //   => if you dragenter from top, you mouse will still be consider as in the top node
       event.persist();
       this.delayedDragEnterLogic[pos] = window.setTimeout(() => {
-        if (!this.state.dragging) return;
+        if (this.state.draggingNodeKey === null) return;
 
         let newExpandedKeys = [...expandedKeys];
         const entity = keyEntities[node.props.eventKey];
@@ -750,10 +751,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
   };
 
   cleanDragState = () => {
-    const { dragging } = this.state;
-    if (dragging) {
+    const { draggingNodeKey } = this.state;
+    if (draggingNodeKey !== null) {
       this.setState({
-        dragging: false,
+        draggingNodeKey: null,
         dropPosition: null,
         dropContainerKey: null,
         dropTargetKey: null,
@@ -1252,29 +1253,27 @@ class Tree extends React.Component<TreeProps, TreeState> {
     atomic: boolean = false,
     forceState: Partial<TreeState> | null = null,
   ) => {
-    if (this.destroyed) {
-      return;
-    }
+    if (!this.destroyed) {
+      let needSync = false;
+      let allPassed = true;
+      const newState = {};
+
+      Object.keys(state).forEach(name => {
+        if (name in this.props) {
+          allPassed = false;
+          return;
+        }
 
-    let needSync = false;
-    let allPassed = true;
-    const newState = {};
+        needSync = true;
+        newState[name] = state[name];
+      });
 
-    Object.keys(state).forEach(name => {
-      if (name in this.props) {
-        allPassed = false;
-        return;
+      if (needSync && (!atomic || allPassed)) {
+        this.setState({
+          ...newState,
+          ...forceState,
+        } as TreeState);
       }
-
-      needSync = true;
-      newState[name] = state[name];
-    });
-
-    if (needSync && (!atomic || allPassed)) {
-      this.setState({
-        ...newState,
-        ...forceState,
-      } as TreeState);
     }
   };
 
@@ -1287,7 +1286,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       focused,
       flattenNodes,
       keyEntities,
-      dragging,
+      draggingNodeKey,
       activeKey,
       dropLevelOffset,
       dropContainerKey,
@@ -1308,6 +1307,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       icon,
       switcherIcon,
       draggable,
+      draggableIcon,
       checkable,
       checkStrictly,
       disabled,
@@ -1334,6 +1334,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
           icon,
           switcherIcon,
           draggable,
+          draggingNodeKey,
+          draggableIcon,
           checkable,
           checkStrictly,
           disabled,
@@ -1386,7 +1388,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
             selectable={selectable}
             checkable={!!checkable}
             motion={motion}
-            dragging={dragging}
+            dragging={draggingNodeKey !== null}
             height={height}
             itemHeight={itemHeight}
             virtual={virtual}
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index f42c4bdd..c68b7842 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -319,6 +319,29 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     return treeSelectable;
   }
 
+  isDraggable = () => {
+    const {
+      data,
+      context: { draggable },
+    } = this.props;
+    const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
+
+    return mergedDraggable;
+  };
+
+  // ==================== Render: Drag Handler ====================
+  renderDragHandler = () => {
+    const {
+      context: { draggable, draggableIcon, prefixCls },
+    } = this.props;
+    const rootDraggable = draggable !== false;
+
+    return rootDraggable && draggableIcon ? (
+      <span className={`${prefixCls}-draggable-icon`}>{draggableIcon}</span>
+    ) : null;
+  };
+
+  // ====================== Render: Switcher ======================
   renderSwitcherIconDom = (isLeaf: boolean) => {
     const { switcherIcon: switcherIconFromProps } = this.props;
     const {
@@ -365,6 +388,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     ) : null;
   };
 
+  // ====================== Render: Checkbox ======================
   // Checkbox
   renderCheckbox = () => {
     const { checked, halfChecked, disableCheckbox } = this.props;
@@ -394,6 +418,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     );
   };
 
+  // ==================== Render: Title + Icon ====================
   renderIcon = () => {
     const { loading } = this.props;
     const {
@@ -416,10 +441,9 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const { dragNodeHighlight } = this.state;
     const { title, selected, icon, loading, data } = this.props;
     const {
-      context: { prefixCls, showIcon, icon: treeIcon, draggable, loadData, titleRender },
+      context: { prefixCls, showIcon, icon: treeIcon, loadData, titleRender },
     } = this.props;
     const disabled = this.isDisabled();
-    const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
 
     const wrapClass = `${prefixCls}-node-content-wrapper`;
 
@@ -460,16 +484,12 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           `${wrapClass}`,
           `${wrapClass}-${this.getNodeState() || 'normal'}`,
           !disabled && (selected || dragNodeHighlight) && `${prefixCls}-node-selected`,
-          !disabled && mergedDraggable && 'draggable',
         )}
-        draggable={(!disabled && mergedDraggable) || undefined}
-        aria-grabbed={(!disabled && mergedDraggable) || undefined}
         onMouseEnter={this.onMouseEnter}
         onMouseLeave={this.onMouseLeave}
         onContextMenu={this.onContextMenu}
         onClick={this.onSelectorClick}
         onDoubleClick={this.onSelectorDoubleClick}
-        onDragStart={mergedDraggable ? this.onDragStart : undefined}
       >
         {$icon}
         {$title}
@@ -478,6 +498,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     );
   };
 
+  // =================== Render: Drop Indicator ===================
   renderDropIndicator = () => {
     const { disabled, eventKey } = this.props;
     const {
@@ -492,14 +513,15 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
         direction,
       },
     } = this.props;
-    const mergedDraggable = draggable !== false;
+    const rootDraggable = draggable !== false;
     // allowDrop is calculated in Tree.tsx, there is no need for calc it here
-    const showIndicator = !disabled && mergedDraggable && dragOverNodeKey === eventKey;
+    const showIndicator = !disabled && rootDraggable && dragOverNodeKey === eventKey;
     return showIndicator
       ? dropIndicatorRender({ dropPosition, dropLevelOffset, indent, prefixCls, direction })
       : null;
   };
 
+  // =========================== Render ===========================
   render() {
     const {
       eventKey,
@@ -526,17 +548,22 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       context: {
         prefixCls,
         filterTreeNode,
-        draggable,
         keyEntities,
         dropContainerKey,
         dropTargetKey,
+        draggingNodeKey,
       },
     } = this.props;
     const disabled = this.isDisabled();
     const dataOrAriaAttributeProps = getDataAndAria(otherProps);
     const { level } = keyEntities[eventKey] || {};
     const isEndNode = isEnd[isEnd.length - 1];
-    const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
+
+    const mergedDraggable = this.isDraggable();
+    const draggableWithoutDisabled = !disabled && mergedDraggable;
+
+    const dragging = draggingNodeKey === eventKey;
+
     return (
       <div
         ref={domRef}
@@ -549,7 +576,9 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           [`${prefixCls}-treenode-loading`]: loading,
           [`${prefixCls}-treenode-active`]: active,
           [`${prefixCls}-treenode-leaf-last`]: isEndNode,
+          [`${prefixCls}-treenode-draggable`]: draggableWithoutDisabled,
 
+          dragging,
           'drop-target': dropTargetKey === eventKey,
           'drop-container': dropContainerKey === eventKey,
           'drag-over': !disabled && dragOver,
@@ -558,6 +587,11 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           'filter-node': filterTreeNode && filterTreeNode(convertNodePropsToEventData(this.props)),
         })}
         style={style}
+        // Draggable config
+        draggable={draggableWithoutDisabled}
+        aria-grabbed={dragging}
+        onDragStart={draggableWithoutDisabled ? this.onDragStart : undefined}
+        // Drop config
         onDragEnter={mergedDraggable ? this.onDragEnter : undefined}
         onDragOver={mergedDraggable ? this.onDragOver : undefined}
         onDragLeave={mergedDraggable ? this.onDragLeave : undefined}
@@ -567,6 +601,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
         {...dataOrAriaAttributeProps}
       >
         <Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
+        {this.renderDragHandler()}
         {this.renderSwitcher()}
         {this.renderCheckbox()}
         {this.renderSelector()}
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index ead1383e..d44a2ba0 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -39,6 +39,8 @@ export interface TreeContextProps {
   icon: IconType;
   switcherIcon: IconType;
   draggable: ((node: DataNode) => boolean) | boolean;
+  draggingNodeKey?: React.Key;
+  draggableIcon?: React.ReactNode;
   checkable: boolean | React.ReactNode;
   checkStrictly: boolean;
   disabled: boolean;
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index 9dc0e2d3..e5e81143 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1,5 +1,5 @@
 /* eslint-disable no-undef, react/no-multi-comp, no-console,
-react/no-unused-state, react/prop-types, no-return-assign, import/no-named-as-default-member */
+react/no-unused-state, react/prop-types, no-return-assign */
 import React from 'react';
 import { render, mount } from 'enzyme';
 import { renderToJson } from 'enzyme-to-json';
@@ -862,709 +862,6 @@ describe('Tree Basic', () => {
     expect(then).toHaveBeenCalled();
   });
 
-  describe('drag and drop', () => {
-    function createTree(props) {
-      return (
-        <Tree draggable defaultExpandAll {...props}>
-          <TreeNode title="parent 1" key="0-0">
-            <TreeNode className="dragTarget" title="leaf" key="0-0-0-0" />
-            <TreeNode className="dropTarget" title="leaf" key="0-0-0-1" />
-          </TreeNode>
-        </Tree>
-      );
-    }
-
-    it('fires dragStart event', () => {
-      const onDragStart = jest.fn();
-      const wrapper = mount(createTree({ onDragStart }));
-      const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
-      treeNode.simulate('dragStart');
-      const event = onDragStart.mock.calls[0][0];
-      expect(event.node).toEqual(node);
-    });
-
-    it('fires dragEnter event', async () => {
-      const onDragEnter = jest.fn();
-      const wrapper = mount(createTree({ onDragEnter }));
-
-      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-
-      // Not trigger self
-      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
-      await delay(900);
-      expect(onDragEnter).not.toHaveBeenCalled();
-
-      wrapper.find('.dropTarget').at(0).simulate('dragEnter');
-      expect(onDragEnter).toHaveBeenCalled();
-
-      await delay(900);
-      wrapper.update();
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-      const event = onDragEnter.mock.calls[0][0];
-      expect(event.node.key).toEqual(node.key);
-      expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
-      expect(onDragEnter).toHaveBeenCalledTimes(1);
-    });
-
-    it('fires dragOver event', () => {
-      const onDragOver = jest.fn();
-      const wrapper = mount(createTree({ onDragOver }));
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      wrapper.find('.dropTarget').at(0).simulate('dragOver');
-      const event = onDragOver.mock.calls[0][0];
-      expect(event.node).toEqual(node);
-    });
-
-    it('fires dragLeave event', () => {
-      const onDragLeave = jest.fn();
-      const wrapper = mount(createTree({ onDragLeave }));
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-      wrapper.find('.dropTarget').at(0).simulate('dragLeave');
-      const event = onDragLeave.mock.calls[0][0];
-      expect(event.node).toEqual(node);
-    });
-
-    it('fires drop event', () => {
-      const onDrop = jest.fn();
-      const wrapper = mount(createTree({ onDrop }));
-      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
-      wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
-      const dropNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-      const dragNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
-      wrapper.find('.dropTarget').at(0).simulate('drop');
-      const event = onDrop.mock.calls[0][0];
-      expect(event.node).toEqual(dropNode);
-      expect(event.dragNode).toEqual(dragNode);
-      expect(event.dragNodesKeys).toEqual(['0-0-0-0']);
-    });
-
-    it('fires dropEnd event', () => {
-      const onDragEnd = jest.fn();
-      const wrapper = mount(createTree({ onDragEnd }));
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
-      wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
-      const event = onDragEnd.mock.calls[0][0];
-      expect(event.node).toEqual(node);
-    });
-
-    it('do not throw error when drag into another non-drag-able tree', () => {
-      const wrapper = mount(
-        <div>
-          {createTree()}
-          {createTree({ draggable: false })}
-        </div>,
-      );
-
-      const dragTree = wrapper.find(Tree).at(0);
-      const normalTree = wrapper.find(Tree).at(1);
-
-      dragTree.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-      normalTree.find('.dropTarget').at(0).simulate('dragEnter');
-      normalTree.find('.dropTarget').at(0).simulate('dragOver');
-      normalTree.find('.dropTarget').at(0).simulate('drop');
-    });
-
-    describe('full steps', () => {
-      function dropTarget(targetSelector) {
-        return new Promise(resolve => {
-          const wrapper = mount(
-            <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
-              <TreeNode key="0-0" className="dragTarget">
-                <TreeNode key="0-0-0" className="dragTargetChild" />
-              </TreeNode>
-              <TreeNode key="0-1">
-                <TreeNode key="0-1-0" />
-              </TreeNode>
-              <TreeNode key="0-2" className="dropTarget">
-                <TreeNode key="0-2-0" />
-              </TreeNode>
-            </Tree>,
-          );
-
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-
-          // 1. Move into target (first in the middle of the node)
-          wrapper.find(targetSelector).simulate('dragEnter', { clientY: 10 });
-          setTimeout(() => {
-            wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
-
-            // 2. Move out of target
-            wrapper.find(targetSelector).simulate('dragLeave');
-
-            // 3. Move in again
-            wrapper.find(targetSelector).simulate('dragEnter', { clientY: 0 });
-            setTimeout(() => {
-              wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
-              // 4. Drop
-              wrapper.find(targetSelector).simulate('drop');
-              wrapper.find('div.dragTarget').simulate('dragEnd');
-
-              resolve();
-            }, 1000);
-          }, 10);
-        });
-      }
-      let domSpy;
-      beforeEach(() => {
-        domSpy = spyElementPrototypes(HTMLElement, {
-          offsetWidth: {
-            get() {
-              return 24;
-            },
-          },
-          getBoundingClientRect: jest.fn(() => ({
-            width: 100,
-            height: 20,
-            top: 0,
-            left: 0,
-            bottom: 20,
-            right: 100,
-          })),
-        });
-        // Object.defineProperties(window.HTMLElement.prototype, {
-        //   // mock indent as 24
-        //   // no need for clearing it, since jest make each file a independent env
-        //   offsetWidth: {
-        //     get() {
-        //       return 24;
-        //     },
-        //   },
-        // });
-      });
-
-      afterEach(() => {
-        domSpy.mockRestore();
-      });
-
-      it('self', () => dropTarget('div.dragTarget'));
-
-      it('target', () => dropTarget('div.dropTarget'));
-    });
-
-    describe('new drop logic', () => {
-      let domSpy;
-      beforeEach(() => {
-        domSpy = spyElementPrototypes(HTMLElement, {
-          getBoundingClientRect: () => ({
-            width: 100,
-            height: 20,
-            top: 0,
-            left: 0,
-            bottom: 20,
-            right: 100,
-          }),
-          offsetWidth: {
-            get() {
-              return 24;
-            },
-          },
-        });
-      });
-      afterEach(() => {
-        domSpy.mockRestore();
-      });
-      ['ltr', 'rtl'].forEach(dir => {
-        const base = dir === 'ltr' ? 1 : -1;
-        it('not allow cross level dnd on expanded nodes', () => {
-          const onDrop = jest.fn();
-          const wrapper = mount(
-            <Tree draggable onDrop={onDrop} direction={dir} expandedKeys={['0-0', '0-1', '0-1-0']}>
-              <TreeNode key="0-0">
-                <TreeNode key="0-0-0" className="dropTarget1">
-                  <TreeNode key="0-0-0-0" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-1">
-                <TreeNode key="0-1-0" className="dropTarget2">
-                  <TreeNode key="0-1-0-0" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-2" className="dragTarget" />
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('drop');
-          // insert after 0-0, since 0-0-0 is not expanded
-          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0');
-          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('drop');
-          // insert into 0-1-0, since it is expanded, do not allow cross level dnd
-          expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0');
-          expect(onDrop.mock.calls[1][0].dropPosition).toEqual(0);
-        });
-        it('allowDrop all nodes', () => {
-          const onDrop = jest.fn();
-          const wrapper = mount(
-            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-              <TreeNode key="0-0" className="dragTargetParent">
-                <TreeNode key="0-0-0" className="dragTarget">
-                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-1">
-                <TreeNode key="0-1-0">
-                  <TreeNode key="0-1-0-0" className="dropTarget" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-2" />
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
-          // (in ltr) drag from right to left, should be insert after, so drop position is 1
-          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 500,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 500,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
-          expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 550,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 550,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 600,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 600,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
-        });
-        it('allowDrop no node', () => {
-          const onDrop = jest.fn();
-          const wrapper = mount(
-            <Tree
-              draggable
-              defaultExpandAll
-              onDrop={onDrop}
-              allowDrop={() => false}
-              direction={dir}
-            >
-              <TreeNode key="0-0" className="dragTargetParent">
-                <TreeNode key="0-0-0" className="dragTarget">
-                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-1">
-                <TreeNode key="0-1-0" className="dropTargetParent">
-                  <TreeNode key="0-1-0-0" className="dropTarget" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-2" />
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
-          // not allow any dropPosition except 0 on expanded node
-          expect(onDrop).not.toHaveBeenCalled();
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop).not.toHaveBeenCalled();
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 500,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 500,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop).not.toHaveBeenCalled();
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 550,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 550,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop).not.toHaveBeenCalled();
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 600,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 600,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop).not.toHaveBeenCalled();
-        });
-        it('drop to top half of first node', () => {
-          const onDrop = jest.fn();
-          const wrapper = mount(
-            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-              <TreeNode key="0-1" className="dropTarget">
-                <TreeNode key="0-1-0">
-                  <TreeNode key="0-1-0-0" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-0" className="dragTargetParent">
-                <TreeNode key="0-0-0" className="dragTarget">
-                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
-                </TreeNode>
-              </TreeNode>
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 400,
-            clientY: 0,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 400,
-            clientY: -1000,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
-          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
-        });
-        it('can drop on its direct parent', () => {
-          const onDrop = jest.fn();
-          const wrapper = mount(
-            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-              <TreeNode key="0-1" className="dropTarget">
-                <TreeNode key="0-1-0">
-                  <TreeNode key="0-1-0-0" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-0" className="dragTargetParent">
-                <TreeNode key="0-0-0" className="dragTarget">
-                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
-                </TreeNode>
-              </TreeNode>
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop).toHaveBeenCalled();
-        });
-        it('cover window dragend & componentWillUnmount', () => {
-          const wrapper = mount(
-            <Tree draggable defaultExpandAll direction={dir}>
-              <TreeNode key="0-1" className="dropTarget">
-                <TreeNode key="0-1-0">
-                  <TreeNode key="0-1-0-0" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-0" className="dragTargetParent">
-                <TreeNode key="0-0-0" className="dragTarget">
-                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
-                </TreeNode>
-              </TreeNode>
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          window.dispatchEvent(new Event('dragend'));
-          expect(wrapper.instance().state.dragging).toEqual(false);
-          wrapper.unmount();
-        });
-        it('dragover first half of non-first child', () => {
-          const onDrop = jest.fn();
-          const wrapper = mount(
-            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-              <TreeNode key="0-0" className="dragTargetParent">
-                <TreeNode key="0-0-0" className="dragTarget">
-                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
-                </TreeNode>
-                <TreeNode key="0-0-1" />
-                <TreeNode key="0-0-2" className="dropTarget" />
-              </TreeNode>
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 500,
-            clientY: 1,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 500,
-            clientY: 1,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
-          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
-        });
-        it('dragover self', () => {
-          const onDrop = jest.fn();
-          const wrapper = mount(
-            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-              <TreeNode key="0-1" className="dropTarget">
-                <TreeNode key="0-1-0">
-                  <TreeNode key="0-1-0-0" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-0" className="dragTargetParent">
-                <TreeNode key="0-0-0" className="dragTarget">
-                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
-                </TreeNode>
-              </TreeNode>
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 400,
-            clientY: 500,
-          });
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 600,
-            clientY: 500,
-          });
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 600,
-            clientY: 500,
-          });
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 600,
-            clientY: 500,
-          });
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop).not.toHaveBeenCalled();
-        });
-        it('not allowDrop on node which has children', () => {
-          const onDrop = jest.fn();
-          const allowDrop = ({ dropNode, dropPosition }) => {
-            if (!dropNode.children) {
-              if (dropPosition === 0) return false;
-            }
-            return true;
-          };
-          const wrapper = mount(
-            <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
-              <TreeNode key="0-0" className="dragTarget">
-                <TreeNode key="0-0-0" className="dragTargetChild" />
-              </TreeNode>
-              <TreeNode key="0-1">
-                <TreeNode key="0-1-0">
-                  <TreeNode key="0-1-0-0" className="dropTarget" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-2" />
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
-          // (in ltr) drag from right to left, should be insert after, so drop position is 1
-          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 500,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 500,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
-          expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 550,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 550,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 600,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 600,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
-        });
-        it('allowDrop should pass dragNode and dropNode', () => {
-          const onDrop = jest.fn();
-          const allowDrop = jest.fn();
-          const wrapper = mount(
-            <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
-              <TreeNode key="0-0" className="dragTarget">
-                <TreeNode key="0-0-0" className="dragTargetChild" />
-              </TreeNode>
-              <TreeNode key="0-1">
-                <TreeNode key="0-1-0">
-                  <TreeNode key="0-1-0-0" className="dropTarget" />
-                </TreeNode>
-              </TreeNode>
-              <TreeNode key="0-2" />
-            </Tree>,
-          );
-          wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-            clientX: base * 500,
-            clientY: 500,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-            clientX: base * 400,
-            clientY: 600,
-          });
-          wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-          expect(allowDrop.mock.calls[0][0].dragNode.key).toEqual('0-0');
-          expect(allowDrop.mock.calls[0][0].dropNode.key).toEqual('0-1-0-0');
-        });
-      });
-    });
-  });
-
   it('renders without errors', () => {
     expect(() => {
       mount(
diff --git a/tests/TreeDraggable.spec.js b/tests/TreeDraggable.spec.js
new file mode 100644
index 00000000..f1f576e8
--- /dev/null
+++ b/tests/TreeDraggable.spec.js
@@ -0,0 +1,737 @@
+/* eslint-disable no-undef, react/no-multi-comp, no-console,
+react/no-unused-state, react/prop-types, no-return-assign */
+import React from 'react';
+import { mount } from 'enzyme';
+import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
+import Tree, { TreeNode } from '../src';
+import { InternalTreeNode } from '../src/TreeNode';
+import { spyConsole } from './util';
+import { convertNodePropsToEventData } from '../src/utils/treeUtil';
+
+const delay = timeout =>
+  new Promise(resolve => {
+    setTimeout(resolve, timeout);
+  });
+
+describe('Tree Draggable', () => {
+  spyConsole();
+
+  function createTree(props) {
+    return (
+      <Tree draggable defaultExpandAll {...props}>
+        <TreeNode title="parent 1" key="0-0">
+          <TreeNode className="dragTarget" title="leaf" key="0-0-0-0" />
+          <TreeNode className="dropTarget" title="leaf" key="0-0-0-1" />
+        </TreeNode>
+      </Tree>
+    );
+  }
+
+  it('fires dragStart event', () => {
+    const onDragStart = jest.fn();
+    const wrapper = mount(createTree({ onDragStart }));
+    const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
+    treeNode.simulate('dragStart');
+    const event = onDragStart.mock.calls[0][0];
+    expect(event.node).toEqual(node);
+  });
+
+  it('fires dragEnter event', async () => {
+    const onDragEnter = jest.fn();
+    const wrapper = mount(createTree({ onDragEnter }));
+
+    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+
+    // Not trigger self
+    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
+    await delay(900);
+    expect(onDragEnter).not.toHaveBeenCalled();
+
+    wrapper.find('.dropTarget').at(0).simulate('dragEnter');
+    expect(onDragEnter).toHaveBeenCalled();
+
+    await delay(900);
+    wrapper.update();
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+    const event = onDragEnter.mock.calls[0][0];
+    expect(event.node.key).toEqual(node.key);
+    expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
+    expect(onDragEnter).toHaveBeenCalledTimes(1);
+  });
+
+  it('fires dragOver event', () => {
+    const onDragOver = jest.fn();
+    const wrapper = mount(createTree({ onDragOver }));
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+    wrapper.find('.dropTarget').at(0).simulate('dragOver');
+    const event = onDragOver.mock.calls[0][0];
+    expect(event.node).toEqual(node);
+  });
+
+  it('fires dragLeave event', () => {
+    const onDragLeave = jest.fn();
+    const wrapper = mount(createTree({ onDragLeave }));
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+    wrapper.find('.dropTarget').at(0).simulate('dragLeave');
+    const event = onDragLeave.mock.calls[0][0];
+    expect(event.node).toEqual(node);
+  });
+
+  it('fires drop event', () => {
+    const onDrop = jest.fn();
+    const wrapper = mount(createTree({ onDrop }));
+    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+    wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
+    wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
+    const dropNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
+    const dragNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
+    wrapper.find('.dropTarget').at(0).simulate('drop');
+    const event = onDrop.mock.calls[0][0];
+    expect(event.node).toEqual(dropNode);
+    expect(event.dragNode).toEqual(dragNode);
+    expect(event.dragNodesKeys).toEqual(['0-0-0-0']);
+  });
+
+  it('fires dropEnd event', () => {
+    const onDragEnd = jest.fn();
+    const wrapper = mount(createTree({ onDragEnd }));
+    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
+    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
+    const event = onDragEnd.mock.calls[0][0];
+    expect(event.node).toEqual(node);
+  });
+
+  it('do not throw error when drag into another non-drag-able tree', () => {
+    const wrapper = mount(
+      <div>
+        {createTree()}
+        {createTree({ draggable: false })}
+      </div>,
+    );
+
+    const dragTree = wrapper.find(Tree).at(0);
+    const normalTree = wrapper.find(Tree).at(1);
+
+    dragTree.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+    normalTree.find('.dropTarget').at(0).simulate('dragEnter');
+    normalTree.find('.dropTarget').at(0).simulate('dragOver');
+    normalTree.find('.dropTarget').at(0).simulate('drop');
+  });
+
+  describe('full steps', () => {
+    function dropTarget(targetSelector) {
+      return new Promise(resolve => {
+        const wrapper = mount(
+          <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
+            <TreeNode key="0-0" className="dragTarget">
+              <TreeNode key="0-0-0" className="dragTargetChild" />
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0" />
+            </TreeNode>
+            <TreeNode key="0-2" className="dropTarget">
+              <TreeNode key="0-2-0" />
+            </TreeNode>
+          </Tree>,
+        );
+
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+
+        // 1. Move into target (first in the middle of the node)
+        wrapper.find(targetSelector).simulate('dragEnter', { clientY: 10 });
+        setTimeout(() => {
+          wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
+
+          // 2. Move out of target
+          wrapper.find(targetSelector).simulate('dragLeave');
+
+          // 3. Move in again
+          wrapper.find(targetSelector).simulate('dragEnter', { clientY: 0 });
+          setTimeout(() => {
+            wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
+            // 4. Drop
+            wrapper.find(targetSelector).simulate('drop');
+            wrapper.find('div.dragTarget').simulate('dragEnd');
+
+            resolve();
+          }, 1000);
+        }, 10);
+      });
+    }
+    let domSpy;
+    beforeEach(() => {
+      domSpy = spyElementPrototypes(HTMLElement, {
+        offsetWidth: {
+          get() {
+            return 24;
+          },
+        },
+        getBoundingClientRect: jest.fn(() => ({
+          width: 100,
+          height: 20,
+          top: 0,
+          left: 0,
+          bottom: 20,
+          right: 100,
+        })),
+      });
+      // Object.defineProperties(window.HTMLElement.prototype, {
+      //   // mock indent as 24
+      //   // no need for clearing it, since jest make each file a independent env
+      //   offsetWidth: {
+      //     get() {
+      //       return 24;
+      //     },
+      //   },
+      // });
+    });
+
+    afterEach(() => {
+      domSpy.mockRestore();
+    });
+
+    it('self', () => dropTarget('div.dragTarget'));
+
+    it('target', () => dropTarget('div.dropTarget'));
+  });
+
+  describe('new drop logic', () => {
+    let domSpy;
+    beforeEach(() => {
+      domSpy = spyElementPrototypes(HTMLElement, {
+        getBoundingClientRect: () => ({
+          width: 100,
+          height: 20,
+          top: 0,
+          left: 0,
+          bottom: 20,
+          right: 100,
+        }),
+        offsetWidth: {
+          get() {
+            return 24;
+          },
+        },
+      });
+    });
+    afterEach(() => {
+      domSpy.mockRestore();
+    });
+    ['ltr', 'rtl'].forEach(dir => {
+      const base = dir === 'ltr' ? 1 : -1;
+      it('not allow cross level dnd on expanded nodes', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable onDrop={onDrop} direction={dir} expandedKeys={['0-0', '0-1', '0-1-0']}>
+            <TreeNode key="0-0">
+              <TreeNode key="0-0-0" className="dropTarget1">
+                <TreeNode key="0-0-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0" className="dropTarget2">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" className="dragTarget" />
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('drop');
+        // insert after 0-0, since 0-0-0 is not expanded
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('drop');
+        // insert into 0-1-0, since it is expanded, do not allow cross level dnd
+        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0');
+        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(0);
+      });
+      it('allowDrop all nodes', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" />
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
+        // (in ltr) drag from right to left, should be insert after, so drop position is 1
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
+      });
+      it('allowDrop no node', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop} allowDrop={() => false} direction={dir}>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0" className="dropTargetParent">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" />
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
+        // not allow any dropPosition except 0 on expanded node
+        expect(onDrop).not.toHaveBeenCalled();
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+      });
+      it('drop to top half of first node', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 400,
+          clientY: 0,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 400,
+          clientY: -1000,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
+      });
+      it('can drop on its direct parent', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).toHaveBeenCalled();
+      });
+      it('cover window dragend & componentWillUnmount', () => {
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll direction={dir}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        window.dispatchEvent(new Event('dragend'));
+        expect(wrapper.instance().state.draggingNodeKey).toBeFalsy();
+        wrapper.unmount();
+      });
+      it('dragover first half of non-first child', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+              <TreeNode key="0-0-1" />
+              <TreeNode key="0-0-2" className="dropTarget" />
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 500,
+          clientY: 1,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 500,
+          clientY: 1,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+      });
+      it('dragover self', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 400,
+          clientY: 500,
+        });
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 600,
+          clientY: 500,
+        });
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 600,
+          clientY: 500,
+        });
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 600,
+          clientY: 500,
+        });
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+      });
+      it('not allowDrop on node which has children', () => {
+        const onDrop = jest.fn();
+        const allowDrop = ({ dropNode, dropPosition }) => {
+          if (!dropNode.children) {
+            if (dropPosition === 0) return false;
+          }
+          return true;
+        };
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-0" className="dragTarget">
+              <TreeNode key="0-0-0" className="dragTargetChild" />
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" />
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
+        // (in ltr) drag from right to left, should be insert after, so drop position is 1
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 500,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 550,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 600,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
+      });
+      it('allowDrop should pass dragNode and dropNode', () => {
+        const onDrop = jest.fn();
+        const allowDrop = jest.fn();
+        const wrapper = mount(
+          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-0" className="dragTarget">
+              <TreeNode key="0-0-0" className="dragTargetChild" />
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" />
+          </Tree>,
+        );
+        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          clientX: base * 500,
+          clientY: 500,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          clientX: base * 400,
+          clientY: 600,
+        });
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(allowDrop.mock.calls[0][0].dragNode.key).toEqual('0-0');
+        expect(allowDrop.mock.calls[0][0].dropNode.key).toEqual('0-1-0-0');
+      });
+    });
+  });
+
+  it('render handler', () => {
+    const wrapper = mount(
+      <Tree
+        draggable
+        draggableIcon={<span className="handler" />}
+        defaultExpandAll
+        treeData={[
+          {
+            title: 'Parent',
+            key: 'parent',
+            children: [
+              {
+                title: 'Child',
+                key: 'child',
+              },
+            ],
+          },
+        ]}
+      />,
+    );
+
+    expect(wrapper.find('.handler')).toHaveLength(2);
+  });
+});
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index aba2cf5f..54c863ed 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -39,7 +39,9 @@ exports[`Tree Basic check basic render 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -66,7 +68,9 @@ exports[`Tree Basic check basic render 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -97,7 +101,9 @@ exports[`Tree Basic check basic render 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -173,7 +179,9 @@ exports[`Tree Basic check check after data ready works 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -200,7 +208,9 @@ exports[`Tree Basic check check after data ready works 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -276,7 +286,9 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -348,7 +360,9 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -372,7 +386,9 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -441,7 +457,9 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -465,7 +483,9 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -489,7 +509,9 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -517,7 +539,9 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -590,7 +614,9 @@ exports[`Tree Basic renders correctly 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="spe rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -617,7 +643,9 @@ exports[`Tree Basic renders correctly 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -648,7 +676,9 @@ exports[`Tree Basic renders correctly 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -682,7 +712,9 @@ exports[`Tree Basic renders correctly 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -716,7 +748,9 @@ exports[`Tree Basic renders correctly 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -792,7 +826,9 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.js.snap
index e2f43eb8..1b05ed68 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.js.snap
@@ -39,7 +39,9 @@ exports[`TreeNode Props className 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -63,7 +65,9 @@ exports[`TreeNode Props className 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -91,7 +95,9 @@ exports[`TreeNode Props className 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -122,7 +128,9 @@ exports[`TreeNode Props className 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -150,7 +158,9 @@ exports[`TreeNode Props className 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -219,7 +229,9 @@ exports[`TreeNode Props customize icon component 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -292,7 +304,9 @@ exports[`TreeNode Props customize icon element 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -365,7 +379,9 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -431,8 +447,10 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             aria-label="0-0"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -456,8 +474,10 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             </span>
           </div>
           <div
+            aria-grabbed="false"
             aria-label="0-0-0"
             class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -485,8 +505,10 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             </span>
           </div>
           <div
+            aria-grabbed="false"
             aria-label="0-0-0-0"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -517,8 +539,10 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             </span>
           </div>
           <div
+            aria-grabbed="false"
             aria-label="0-0-1"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -546,8 +570,10 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             </span>
           </div>
           <div
+            aria-grabbed="false"
             aria-label="0-1"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -616,8 +642,10 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
             data-test="0-0"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -641,8 +669,10 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
             data-test="0-0-0"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -670,8 +700,10 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             data-test="0-0-0-0"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -702,8 +734,10 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             data-test="0-0-1"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -731,8 +765,10 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             data-test="0-1"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -801,7 +837,9 @@ exports[`TreeNode Props isLeaf 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -822,7 +860,9 @@ exports[`TreeNode Props isLeaf 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -892,7 +932,9 @@ exports[`TreeNode Props isLeaf 2`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -916,7 +958,9 @@ exports[`TreeNode Props isLeaf 2`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -989,7 +1033,9 @@ exports[`TreeNode Props isLeaf 3`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1013,7 +1059,9 @@ exports[`TreeNode Props isLeaf 3`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.js.snap
index 82b0be4f..abe1b745 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.js.snap
@@ -39,7 +39,9 @@ exports[`Tree Props checkStrictly 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -66,7 +68,9 @@ exports[`Tree Props checkStrictly 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -142,7 +146,9 @@ exports[`Tree Props checkable default 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -169,7 +175,9 @@ exports[`Tree Props checkable default 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -245,7 +253,9 @@ exports[`Tree Props checkable without selectable 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -272,7 +282,9 @@ exports[`Tree Props checkable without selectable 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -348,7 +360,9 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -372,7 +386,9 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -400,7 +416,9 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -428,7 +446,9 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -501,7 +521,9 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -529,7 +551,9 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -557,7 +581,9 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -581,7 +607,9 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -613,7 +641,9 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -645,7 +675,9 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -808,7 +840,9 @@ exports[`Tree Props defaultExpandAll 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -832,7 +866,9 @@ exports[`Tree Props defaultExpandAll 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -906,7 +942,9 @@ exports[`Tree Props disabled basic 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -976,7 +1014,9 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1045,7 +1085,9 @@ exports[`Tree Props icon 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1073,7 +1115,9 @@ exports[`Tree Props icon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1150,7 +1194,9 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1177,7 +1223,9 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1253,7 +1301,9 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1280,7 +1330,9 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1356,7 +1408,9 @@ exports[`Tree Props loadData basic 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1377,7 +1431,9 @@ exports[`Tree Props loadData basic 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1447,7 +1503,9 @@ exports[`Tree Props multiple 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1471,7 +1529,9 @@ exports[`Tree Props multiple 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1632,7 +1692,9 @@ exports[`Tree Props selectable with selectable 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1656,7 +1718,9 @@ exports[`Tree Props selectable with selectable 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1729,7 +1793,9 @@ exports[`Tree Props selectable without selectable 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1753,7 +1819,9 @@ exports[`Tree Props selectable without selectable 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1826,7 +1894,9 @@ exports[`Tree Props showIcon 1`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1850,7 +1920,9 @@ exports[`Tree Props showIcon 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1919,7 +1991,9 @@ exports[`Tree Props showIcon 2`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -1940,7 +2014,9 @@ exports[`Tree Props showIcon 2`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2006,7 +2082,9 @@ exports[`Tree Props showIcon 3`] = `
           style="display:flex;flex-direction:column"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2034,7 +2112,9 @@ exports[`Tree Props showIcon 3`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2062,7 +2142,9 @@ exports[`Tree Props showIcon 3`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2093,7 +2175,9 @@ exports[`Tree Props showIcon 3`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2121,7 +2205,9 @@ exports[`Tree Props showIcon 3`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2234,7 +2320,9 @@ exports[`Tree Props treeData 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2258,7 +2346,9 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2282,7 +2372,9 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2310,7 +2402,9 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2338,7 +2432,9 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2369,7 +2465,9 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2400,7 +2498,9 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"
@@ -2473,7 +2573,9 @@ exports[`Tree Props treeData 2`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
+            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            draggable="false"
           >
             <span
               aria-hidden="true"

From be23dc1bca2ee7f9dda8faa60cecc84b37e0fe26 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Sun, 26 Sep 2021 15:07:34 +0800
Subject: [PATCH 210/397] 5.1.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d7dcba16..980aabe8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.0.5",
+  "version": "5.1.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From b82a632a4d30f6f99a25f17f9991ec350f7dcaf7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 27 Sep 2021 11:51:04 +0800
Subject: [PATCH 211/397] refactor: Merge draggable api (#491)

---
 docs/examples/draggable.jsx |  5 +++--
 src/Tree.tsx                | 27 ++++++++++++++++++++++-----
 src/TreeNode.tsx            | 10 ++++------
 src/contextTypes.ts         |  4 ++--
 tests/TreeDraggable.spec.js |  5 +++--
 5 files changed, 34 insertions(+), 17 deletions(-)

diff --git a/docs/examples/draggable.jsx b/docs/examples/draggable.jsx
index f52142cc..a020478a 100644
--- a/docs/examples/draggable.jsx
+++ b/docs/examples/draggable.jsx
@@ -110,8 +110,9 @@ class Demo extends React.Component {
             expandedKeys={this.state.expandedKeys}
             onExpand={this.onExpand}
             autoExpandParent={this.state.autoExpandParent}
-            draggable
-            draggableIcon="↕️"
+            draggable={{
+              icon: '↕️',
+            }}
             onDragStart={this.onDragStart}
             onDragEnter={this.onDragEnter}
             onDrop={this.onDrop}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 59f65247..8ff5961b 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -67,6 +67,12 @@ export interface AllowDropOptions {
 }
 export type AllowDrop = (options: AllowDropOptions) => boolean;
 
+export type DraggableFn = (node: DataNode) => boolean;
+export type DraggableConfig = {
+  icon?: React.ReactNode | false;
+  nodeDraggable?: DraggableFn;
+};
+
 export interface TreeProps {
   prefixCls: string;
   className?: string;
@@ -84,8 +90,7 @@ export interface TreeProps {
   multiple?: boolean;
   checkable?: boolean | React.ReactNode;
   checkStrictly?: boolean;
-  draggable?: ((node: DataNode) => boolean) | boolean;
-  draggableIcon?: React.ReactNode;
+  draggable?: DraggableFn | boolean | DraggableConfig;
   defaultExpandParent?: boolean;
   autoExpandParent?: boolean;
   defaultExpandAll?: boolean;
@@ -1307,7 +1312,6 @@ class Tree extends React.Component<TreeProps, TreeState> {
       icon,
       switcherIcon,
       draggable,
-      draggableIcon,
       checkable,
       checkStrictly,
       disabled,
@@ -1325,6 +1329,20 @@ class Tree extends React.Component<TreeProps, TreeState> {
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
 
+    // It's better move to hooks but we just simply keep here
+    let draggableConfig: DraggableConfig;
+    if (draggable) {
+      if (typeof draggable === 'object') {
+        draggableConfig = draggable;
+      } else if (typeof draggable === 'function') {
+        draggableConfig = {
+          nodeDraggable: draggable,
+        };
+      } else {
+        draggableConfig = {};
+      }
+    }
+
     return (
       <TreeContext.Provider
         value={{
@@ -1333,9 +1351,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
           showIcon,
           icon,
           switcherIcon,
-          draggable,
+          draggable: draggableConfig,
           draggingNodeKey,
-          draggableIcon,
           checkable,
           checkStrictly,
           disabled,
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index c68b7842..56a8d16d 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -324,20 +324,18 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       data,
       context: { draggable },
     } = this.props;
-    const mergedDraggable = typeof draggable === 'function' ? draggable(data) : draggable;
 
-    return mergedDraggable;
+    return !!(draggable && (!draggable.nodeDraggable || draggable.nodeDraggable(data)));
   };
 
   // ==================== Render: Drag Handler ====================
   renderDragHandler = () => {
     const {
-      context: { draggable, draggableIcon, prefixCls },
+      context: { draggable, prefixCls },
     } = this.props;
-    const rootDraggable = draggable !== false;
 
-    return rootDraggable && draggableIcon ? (
-      <span className={`${prefixCls}-draggable-icon`}>{draggableIcon}</span>
+    return draggable?.icon ? (
+      <span className={`${prefixCls}-draggable-icon`}>{draggable.icon}</span>
     ) : null;
   };
 
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index d44a2ba0..1d246a0c 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -12,6 +12,7 @@ import {
   DataNode,
   Direction,
 } from './interface';
+import { DraggableConfig } from './Tree';
 
 export type NodeMouseEventParams<T = HTMLSpanElement> = {
   event: React.MouseEvent<T>;
@@ -38,9 +39,8 @@ export interface TreeContextProps {
   showIcon: boolean;
   icon: IconType;
   switcherIcon: IconType;
-  draggable: ((node: DataNode) => boolean) | boolean;
+  draggable?: DraggableConfig;
   draggingNodeKey?: React.Key;
-  draggableIcon?: React.ReactNode;
   checkable: boolean | React.ReactNode;
   checkStrictly: boolean;
   disabled: boolean;
diff --git a/tests/TreeDraggable.spec.js b/tests/TreeDraggable.spec.js
index f1f576e8..9b26e4da 100644
--- a/tests/TreeDraggable.spec.js
+++ b/tests/TreeDraggable.spec.js
@@ -714,8 +714,9 @@ describe('Tree Draggable', () => {
   it('render handler', () => {
     const wrapper = mount(
       <Tree
-        draggable
-        draggableIcon={<span className="handler" />}
+        draggable={{
+          icon: <span className="handler" />,
+        }}
         defaultExpandAll
         treeData={[
           {

From d69a71b0ece11f910fa1daa1b88623a523512e74 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 27 Sep 2021 14:23:15 +0800
Subject: [PATCH 212/397] 5.2.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 980aabe8..c486a468 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.1.0",
+  "version": "5.2.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From fe961d24dbb548e39570e23f973f26b53a76c06d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Wed, 29 Sep 2021 11:25:09 +0800
Subject: [PATCH 213/397] chore: Retry 10 times after failed (#493)

* chore: add max retry ant-design/ant-design#32313

* test: test case
---
 src/Tree.tsx            | 31 ++++++++++++++++++++++++++++---
 tests/TreeProps.spec.js | 25 +++++++++++++++++++++++++
 2 files changed, 53 insertions(+), 3 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 8ff5961b..c35d23e4 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -50,6 +50,8 @@ import TreeNode from './TreeNode';
 import { conductCheck } from './utils/conductUtil';
 import DropIndicator from './DropIndicator';
 
+const MAX_RETRY_TIMES = 10;
+
 interface CheckInfo {
   event: 'check';
   node: EventDataNode;
@@ -243,6 +245,8 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   delayedDragEnterLogic: Record<Key, number>;
 
+  loadingRetryTimes: Record<Key, number> = {};
+
   state: TreeState = {
     keyEntities: {},
 
@@ -911,12 +915,13 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeLoad = (treeNode: EventDataNode) =>
-    new Promise<void>((resolve, reject) => {
+  onNodeLoad = (treeNode: EventDataNode) => {
+    const { key } = treeNode;
+
+    const loadPromise = new Promise<void>((resolve, reject) => {
       // We need to get the latest state of loading/loaded keys
       this.setState(({ loadedKeys = [], loadingKeys = [] }): any => {
         const { loadData, onLoad } = this.props;
-        const { key } = treeNode;
 
         if (!loadData || loadedKeys.indexOf(key) !== -1 || loadingKeys.indexOf(key) !== -1) {
           return null;
@@ -951,6 +956,20 @@ class Tree extends React.Component<TreeProps, TreeState> {
             this.setState(prevState => ({
               loadingKeys: arrDel(prevState.loadingKeys, key),
             }));
+
+            // If exceed max retry times, we give up retry
+            this.loadingRetryTimes[key] = (this.loadingRetryTimes[key] || 0) + 1;
+            if (this.loadingRetryTimes[key] >= MAX_RETRY_TIMES) {
+              const { loadedKeys: currentLoadedKeys } = this.state;
+
+              warning(false, 'Retry for `loadData` many times but still failed. No more retry.');
+
+              this.setUncontrolledState({
+                loadedKeys: arrAdd(currentLoadedKeys, key),
+              });
+              resolve();
+            }
+
             reject(e);
           });
 
@@ -960,6 +979,12 @@ class Tree extends React.Component<TreeProps, TreeState> {
       });
     });
 
+    // Not care warning if we ignore this
+    loadPromise.catch(() => {});
+
+    return loadPromise;
+  };
+
   onNodeMouseEnter: NodeMouseEventHandler = (event, node) => {
     const { onMouseEnter } = this.props;
     if (onMouseEnter) {
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index ba67b3a9..4f7834dd 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -636,6 +636,31 @@ describe('Tree Props', () => {
         }, 500);
       }, 500);
     });
+
+    it('reject load', async () => {
+      const wrapper = mount(
+        <Tree
+          loadData={() => Promise.reject()}
+          expandedKeys={['parent']}
+          treeData={[
+            {
+              title: 'parent',
+              key: 'parent',
+            },
+          ]}
+        />,
+      );
+
+      // Do delay
+      await timeoutPromise();
+
+      wrapper.update();
+      expect(wrapper.exists('.rc-tree-icon_loading')).toBeFalsy();
+
+      expect(errorSpy()).toHaveBeenCalledWith(
+        'Warning: Retry for `loadData` many times but still failed. No more retry.',
+      );
+    });
   });
 
   it('icon', () => {

From 8ba66a406176c71d07e435fe601b30f7d47c2fa8 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Wed, 29 Sep 2021 11:26:49 +0800
Subject: [PATCH 214/397] 5.2.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c486a468..48ddd6d1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.2.0",
+  "version": "5.2.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From b84aa9022f843217a838c8957e9ef7acdb3a5687 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 25 Oct 2021 17:40:21 +0800
Subject: [PATCH 215/397] fix: field names failed for checkable (#498)

* fix: fieldNames checked logic

* test: test case
---
 docs/examples/fieldNames.tsx | 89 ++++++++++++++++++++++++------------
 src/utils/treeUtil.ts        |  2 +
 tests/FieldNames.spec.tsx    | 27 +++++++++++
 3 files changed, 89 insertions(+), 29 deletions(-)

diff --git a/docs/examples/fieldNames.tsx b/docs/examples/fieldNames.tsx
index db93f661..2f6ce4cf 100644
--- a/docs/examples/fieldNames.tsx
+++ b/docs/examples/fieldNames.tsx
@@ -4,36 +4,67 @@ import '../../assets/index.less';
 import './basic.less';
 import Tree from 'rc-tree';
 
-export default () => {
+const treeData = [
+  {
+    name: 'parent 1',
+    test: '0-0',
+    child: [
+      {
+        name: '张晨成',
+        test: '0-0-0',
+        disabled: true,
+        child: [
+          {
+            name: 'leaf',
+            test: '0-0-0-0',
+            disableCheckbox: true,
+          },
+          {
+            name: 'leaf',
+            test: '0-0-0-1',
+          },
+        ],
+      },
+      {
+        name: 'parent 1-1',
+        test: '0-0-1',
+        child: [
+          {
+            test: '0-0-1-0',
+            name: 'zcvc',
+          },
+        ],
+      },
+    ],
+  },
+];
+
+const Demo = () => {
+  const onSelect = (selectedKeys, info) => {
+    console.log('selected', selectedKeys, info);
+  };
+
+  const onCheck = (checkedKeys, info) => {
+    console.log('onCheck', checkedKeys, info);
+  };
+  const fieldNames = {
+    children: 'child',
+    title: 'name',
+    key: 'test',
+  };
+
   return (
     <Tree
-      // treeData={
-      //   [
-      //     {
-      //       fieldLabel: 'Root',
-      //       fieldKey: 'root',
-      //       fieldChildren: [
-      //         {
-      //           fieldLabel: 'Bamboo',
-      //           fieldKey: 'bamboo',
-      //         },
-      //         {
-      //           fieldLabel: 'Light',
-      //           fieldKey: 'light',
-      //         },
-      //       ],
-      //     },
-      //   ] as any
-      // }
-
-      // fieldNames={{
-      //   title: 'fieldLabel',
-      //   key: 'fieldKey',
-      //   children: 'fieldChildren',
-      // }}
-      defaultExpandAll
-    >
-      <Tree.TreeNode title="Bamboo" key="light" />
-    </Tree>
+      checkable
+      fieldNames={fieldNames}
+      defaultExpandedKeys={['0-0-0', '0-0-1']}
+      defaultSelectedKeys={['0-0-0', '0-0-1']}
+      defaultCheckedKeys={['0-0-0', '0-0-1']}
+      onSelect={onSelect}
+      onCheck={onCheck}
+      treeData={treeData as any}
+    />
   );
 };
+
+export default Demo;
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index e6290711..7e73fcad 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -379,6 +379,7 @@ export function convertNodePropsToEventData(props: TreeNodeProps): EventDataNode
     dragOverGapBottom,
     pos,
     active,
+    eventKey,
   } = props;
 
   const eventData = {
@@ -394,6 +395,7 @@ export function convertNodePropsToEventData(props: TreeNodeProps): EventDataNode
     dragOverGapBottom,
     pos,
     active,
+    key: eventKey,
   };
 
   if (!('props' in eventData)) {
diff --git a/tests/FieldNames.spec.tsx b/tests/FieldNames.spec.tsx
index 3501f451..0b3997e8 100644
--- a/tests/FieldNames.spec.tsx
+++ b/tests/FieldNames.spec.tsx
@@ -112,4 +112,31 @@ describe('FieldNames', () => {
       wrapper.find('.rc-tree-list-holder div.rc-tree-treenode').map(node => node.text()),
     ).toEqual(['New Title', 'New Sub 1', 'New Sub 2', 'New Title 2']);
   });
+
+  it('checkable should work', () => {
+    const onCheck = jest.fn();
+
+    const wrapper = mount(
+      <Tree
+        checkable
+        onCheck={onCheck}
+        fieldNames={{ key: 'val', children: 'child' }}
+        treeData={
+          [
+            {
+              val: 'parent',
+              child: [
+                {
+                  val: 'child',
+                },
+              ],
+            },
+          ] as any
+        }
+      />,
+    );
+
+    wrapper.find('.rc-tree-checkbox').last().simulate('click');
+    expect(onCheck).toHaveBeenCalledWith(['parent', 'child'], expect.anything());
+  });
 });

From 86a7a3187be158e6b47ed5b12c274feee30514ed Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 25 Oct 2021 17:41:15 +0800
Subject: [PATCH 216/397] 5.2.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 48ddd6d1..b3f443f5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.2.1",
+  "version": "5.2.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 1958067dc85777d6121daf0afc21ef5f190d679a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 22 Nov 2021 15:45:10 +0800
Subject: [PATCH 217/397] feat: Tree support generic (#503)

* refactor: Add generic

* test: add ts test case

* fix: aria check

* test: rm useless test
---
 src/NodeList.tsx         |  2 +-
 src/Tree.tsx             | 70 +++++++++++++++++++++++-----------------
 src/TreeNode.tsx         | 12 +++----
 src/contextTypes.ts      | 26 +++++++--------
 src/index.ts             |  3 +-
 src/interface.tsx        | 30 ++++++++++-------
 src/util.tsx             | 42 +++++++++++-------------
 src/utils/conductUtil.ts | 35 +++++++++++---------
 src/utils/treeUtil.ts    | 13 +++++---
 tests/type.spec.tsx      | 28 ++++++++++++++++
 tests/util.spec.js       | 16 ---------
 11 files changed, 155 insertions(+), 122 deletions(-)
 create mode 100644 tests/type.spec.tsx

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 87d426a9..481eab95 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -72,7 +72,7 @@ interface NodeListProps {
   loadedKeys: Key[];
   loadingKeys: Key[];
   halfCheckedKeys: Key[];
-  keyEntities: Record<Key, DataEntity>;
+  keyEntities: Record<Key, DataEntity<any>>;
 
   dragging: boolean;
   dragOverNodeKey: Key;
diff --git a/src/Tree.tsx b/src/Tree.tsx
index c35d23e4..d71756c6 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -4,6 +4,7 @@
 import * as React from 'react';
 import KeyCode from 'rc-util/lib/KeyCode';
 import warning from 'rc-util/lib/warning';
+import pickAttrs from 'rc-util/lib/pickAttrs';
 import classNames from 'classnames';
 
 import {
@@ -14,7 +15,6 @@ import {
   NodeMouseEventParams,
 } from './contextTypes';
 import {
-  getDataAndAria,
   getDragChildrenKeys,
   parseCheckedKeys,
   conductExpandParent,
@@ -35,6 +35,7 @@ import {
   ScrollTo,
   Direction,
   FieldNames,
+  BasicDataNode,
 } from './interface';
 import {
   flattenTreeData,
@@ -52,22 +53,24 @@ import DropIndicator from './DropIndicator';
 
 const MAX_RETRY_TIMES = 10;
 
-interface CheckInfo {
+interface CheckInfo<TreeDataType extends BasicDataNode = DataNode> {
   event: 'check';
   node: EventDataNode;
   checked: boolean;
   nativeEvent: MouseEvent;
-  checkedNodes: DataNode[];
-  checkedNodesPositions?: { node: DataNode; pos: string }[];
+  checkedNodes: TreeDataType[];
+  checkedNodesPositions?: { node: TreeDataType; pos: string }[];
   halfCheckedKeys?: Key[];
 }
 
-export interface AllowDropOptions {
-  dragNode: DataNode;
-  dropNode: DataNode;
+export interface AllowDropOptions<TreeDataType extends BasicDataNode = DataNode> {
+  dragNode: TreeDataType;
+  dropNode: TreeDataType;
   dropPosition: -1 | 0 | 1;
 }
-export type AllowDrop = (options: AllowDropOptions) => boolean;
+export type AllowDrop<TreeDataType extends BasicDataNode = DataNode> = (
+  options: AllowDropOptions<TreeDataType>,
+) => boolean;
 
 export type DraggableFn = (node: DataNode) => boolean;
 export type DraggableConfig = {
@@ -75,14 +78,14 @@ export type DraggableConfig = {
   nodeDraggable?: DraggableFn;
 };
 
-export interface TreeProps {
+export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   prefixCls: string;
   className?: string;
   style?: React.CSSProperties;
   focusable?: boolean;
   tabIndex?: number;
   children?: React.ReactNode;
-  treeData?: DataNode[]; // Generate treeNode by children
+  treeData?: TreeDataType[]; // Generate treeNode by children
   fieldNames?: FieldNames;
   showLine?: boolean;
   showIcon?: boolean;
@@ -102,8 +105,8 @@ export interface TreeProps {
   checkedKeys?: Key[] | { checked: Key[]; halfChecked: Key[] };
   defaultSelectedKeys?: Key[];
   selectedKeys?: Key[];
-  allowDrop?: AllowDrop;
-  titleRender?: (node: DataNode) => React.ReactNode;
+  allowDrop?: AllowDrop<TreeDataType>;
+  titleRender?: (node: TreeDataType) => React.ReactNode;
   dropIndicatorRender?: (props: {
     dropPosition: -1 | 0 | 1;
     dropLevelOffset: number;
@@ -126,14 +129,17 @@ export interface TreeProps {
       nativeEvent: MouseEvent;
     },
   ) => void;
-  onCheck?: (checked: { checked: Key[]; halfChecked: Key[] } | Key[], info: CheckInfo) => void;
+  onCheck?: (
+    checked: { checked: Key[]; halfChecked: Key[] } | Key[],
+    info: CheckInfo<BasicDataNode>,
+  ) => void;
   onSelect?: (
     selectedKeys: Key[],
     info: {
       event: 'select';
       selected: boolean;
       node: EventDataNode;
-      selectedNodes: DataNode[];
+      selectedNodes: TreeDataType[];
       nativeEvent: MouseEvent;
     },
   ) => void;
@@ -180,8 +186,8 @@ export interface TreeProps {
   direction?: Direction;
 }
 
-interface TreeState {
-  keyEntities: Record<Key, DataEntity>;
+interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
+  keyEntities: Record<Key, DataEntity<TreeDataType>>;
 
   indent: number | null;
 
@@ -218,7 +224,10 @@ interface TreeState {
   fieldNames: FieldNames;
 }
 
-class Tree extends React.Component<TreeProps, TreeState> {
+class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Component<
+  TreeProps<TreeDataType>,
+  TreeState<TreeDataType>
+> {
   static defaultProps = {
     prefixCls: 'rc-tree',
     showLine: false,
@@ -247,7 +256,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   loadingRetryTimes: Record<Key, number> = {};
 
-  state: TreeState = {
+  state: TreeState<TreeDataType> = {
     keyEntities: {},
 
     indent: null,
@@ -291,7 +300,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
   dragStartMousePosition = null;
 
-  dragNode: NodeInstance;
+  dragNode: NodeInstance<TreeDataType>;
 
   currentMouseOverDroppableNodeKey = null;
 
@@ -424,7 +433,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     return newState;
   }
 
-  onNodeDragStart: NodeDragEventHandler = (event, node) => {
+  onNodeDragStart: NodeDragEventHandler<HTMLDivElement, TreeDataType> = (event, node) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
     const { eventKey } = node.props;
@@ -459,7 +468,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
    * Better for use mouse move event to refresh drag state.
    * But let's just keep it to avoid event trigger logic change.
    */
-  onNodeDragEnter = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragEnter = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance<TreeDataType>) => {
     const { expandedKeys, keyEntities, dragChildrenKeys, flattenNodes, indent } = this.state;
     const { onDragEnter, onExpand, allowDrop, direction } = this.props;
     const { pos, eventKey } = node.props;
@@ -478,7 +487,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       dropTargetPos,
       dropAllowed,
       dragOverNodeKey,
-    } = calcDropPosition(
+    } = calcDropPosition<TreeDataType>(
       event,
       dragNode,
       node,
@@ -582,7 +591,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
   };
 
-  onNodeDragOver = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance) => {
+  onNodeDragOver = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance<TreeDataType>) => {
     const { dragChildrenKeys, flattenNodes, keyEntities, expandedKeys, indent } = this.state;
     const { onDragOver, allowDrop, direction } = this.props;
     const { dragNode } = this;
@@ -845,7 +854,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
 
     // Prepare trigger arguments
     let checkedObj;
-    const eventObj: Partial<CheckInfo> = {
+    const eventObj: Partial<CheckInfo<TreeDataType>> = {
       event: 'check',
       node: treeNode,
       checked,
@@ -911,7 +920,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
     }
 
     if (onCheck) {
-      onCheck(checkedObj, eventObj as CheckInfo);
+      onCheck(checkedObj, eventObj as CheckInfo<TreeDataType>);
     }
   };
 
@@ -1047,7 +1056,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
       halfCheckedKeys: halfCheckedKeys || [],
       dragOverNodeKey,
       dropPosition,
-      keyEntities,
+      keyEntities: keyEntities,
     };
   };
 
@@ -1281,7 +1290,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
   setUncontrolledState = (
     state: Partial<TreeState>,
     atomic: boolean = false,
-    forceState: Partial<TreeState> | null = null,
+    forceState: Partial<TreeState<TreeDataType>> | null = null,
   ) => {
     if (!this.destroyed) {
       let needSync = false;
@@ -1302,7 +1311,7 @@ class Tree extends React.Component<TreeProps, TreeState> {
         this.setState({
           ...newState,
           ...forceState,
-        } as TreeState);
+        } as TreeState<TreeDataType>);
       }
     }
   };
@@ -1352,7 +1361,10 @@ class Tree extends React.Component<TreeProps, TreeState> {
       onScroll,
       direction,
     } = this.props;
-    const domProps: React.HTMLAttributes<HTMLDivElement> = getDataAndAria(this.props);
+    const domProps: React.HTMLAttributes<HTMLDivElement> = pickAttrs(this.props, {
+      aria: true,
+      data: true,
+    });
 
     // It's better move to hooks but we just simply keep here
     let draggableConfig: DraggableConfig;
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 56a8d16d..17d244fa 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -1,9 +1,9 @@
 import * as React from 'react';
 import classNames from 'classnames';
+import pickAttrs from 'rc-util/lib/pickAttrs';
 // @ts-ignore
 import { TreeContext, TreeContextProps } from './contextTypes';
-import { getDataAndAria } from './util';
-import { IconType, Key, DataNode } from './interface';
+import { IconType, Key, DataNode, BasicDataNode } from './interface';
 import Indent from './Indent';
 import { convertNodePropsToEventData } from './utils/treeUtil';
 
@@ -12,7 +12,7 @@ const ICON_CLOSE = 'close';
 
 const defaultTitle = '---';
 
-export interface TreeNodeProps {
+export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
   eventKey?: Key; // Pass by parent `cloneElement`
   prefixCls?: string;
   className?: string;
@@ -25,14 +25,14 @@ export interface TreeNodeProps {
   loaded?: boolean;
   loading?: boolean;
   halfChecked?: boolean;
-  title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
+  title?: React.ReactNode | ((data: TreeDataType) => React.ReactNode);
   dragOver?: boolean;
   dragOverGapTop?: boolean;
   dragOverGapBottom?: boolean;
   pos?: string;
   domRef?: React.Ref<HTMLDivElement>;
   /** New added in Tree for easy data access */
-  data?: DataNode;
+  data?: TreeDataType;
   isStart?: boolean[];
   isEnd?: boolean[];
   active?: boolean;
@@ -553,7 +553,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       },
     } = this.props;
     const disabled = this.isDisabled();
-    const dataOrAriaAttributeProps = getDataAndAria(otherProps);
+    const dataOrAriaAttributeProps = pickAttrs(otherProps, { aria: true, data: true });
     const { level } = keyEntities[eventKey] || {};
     const isEndNode = isEnd[isEnd.length - 1];
 
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 1d246a0c..4de053f4 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -11,6 +11,7 @@ import {
   NodeInstance,
   DataNode,
   Direction,
+  BasicDataNode,
 } from './interface';
 import { DraggableConfig } from './Tree';
 
@@ -27,11 +28,10 @@ export type NodeMouseEventHandler<T = HTMLSpanElement> = (
   e: React.MouseEvent<T>,
   node: EventDataNode,
 ) => void;
-export type NodeDragEventHandler<T = HTMLDivElement> = (
-  e: React.DragEvent<T>,
-  node: NodeInstance,
-  outsideTree?: boolean,
-) => void;
+export type NodeDragEventHandler<
+  T = HTMLDivElement,
+  TreeDataType extends BasicDataNode = DataNode,
+> = (e: React.DragEvent<T>, node: NodeInstance<TreeDataType>, outsideTree?: boolean) => void;
 
 export interface TreeContextProps {
   prefixCls: string;
@@ -44,7 +44,7 @@ export interface TreeContextProps {
   checkable: boolean | React.ReactNode;
   checkStrictly: boolean;
   disabled: boolean;
-  keyEntities: Record<Key, DataEntity>;
+  keyEntities: Record<Key, DataEntity<any>>;
   // for details see comment in Tree.state (Tree.tsx)
   dropLevelOffset?: number;
   dropContainerKey: Key | null;
@@ -63,7 +63,7 @@ export interface TreeContextProps {
 
   loadData: (treeNode: EventDataNode) => Promise<void>;
   filterTreeNode: (treeNode: EventDataNode) => boolean;
-  titleRender?: (node: DataNode) => React.ReactNode;
+  titleRender?: (node: any) => React.ReactNode;
 
   onNodeClick: NodeMouseEventHandler;
   onNodeDoubleClick: NodeMouseEventHandler;
@@ -78,12 +78,12 @@ export interface TreeContextProps {
   onNodeMouseEnter: NodeMouseEventHandler;
   onNodeMouseLeave: NodeMouseEventHandler;
   onNodeContextMenu: NodeMouseEventHandler;
-  onNodeDragStart: NodeDragEventHandler;
-  onNodeDragEnter: NodeDragEventHandler;
-  onNodeDragOver: NodeDragEventHandler;
-  onNodeDragLeave: NodeDragEventHandler;
-  onNodeDragEnd: NodeDragEventHandler;
-  onNodeDrop: NodeDragEventHandler;
+  onNodeDragStart: NodeDragEventHandler<any, any>;
+  onNodeDragEnter: NodeDragEventHandler<any, any>;
+  onNodeDragOver: NodeDragEventHandler<any, any>;
+  onNodeDragLeave: NodeDragEventHandler<any, any>;
+  onNodeDragEnd: NodeDragEventHandler<any, any>;
+  onNodeDrop: NodeDragEventHandler<any, any>;
 }
 
 export const TreeContext: React.Context<TreeContextProps | null> = React.createContext(null);
diff --git a/src/index.ts b/src/index.ts
index 6df17209..33720af5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,7 +2,8 @@ import Tree from './Tree';
 import TreeNode from './TreeNode';
 import type { TreeProps } from './Tree';
 import type { TreeNodeProps } from './TreeNode';
+import type { BasicDataNode } from './interface';
 
 export { TreeNode };
-export type { TreeProps, TreeNodeProps };
+export type { TreeProps, TreeNodeProps, BasicDataNode };
 export default Tree;
diff --git a/src/interface.tsx b/src/interface.tsx
index b537a5e2..967652e9 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -3,15 +3,13 @@ import { TreeNodeProps } from './TreeNode';
 
 export { ScrollTo } from 'rc-virtual-list/lib/List';
 
-export interface DataNode {
+/** For fieldNames, we provides a abstract interface */
+export interface BasicDataNode {
   checkable?: boolean;
-  children?: DataNode[];
   disabled?: boolean;
   disableCheckbox?: boolean;
   icon?: IconType;
   isLeaf?: boolean;
-  key: string | number;
-  title?: React.ReactNode;
   selectable?: boolean;
   switcherIcon?: IconType;
 
@@ -20,6 +18,12 @@ export interface DataNode {
   style?: React.CSSProperties;
 }
 
+export interface DataNode extends BasicDataNode {
+  children?: DataNode[];
+  key: string | number;
+  title?: React.ReactNode;
+}
+
 export interface EventDataNode extends DataNode {
   expanded: boolean;
   selected: boolean;
@@ -45,9 +49,10 @@ export type NodeElement = React.ReactElement<TreeNodeProps> & {
   };
 };
 
-export type NodeInstance = React.Component<TreeNodeProps> & {
-  selectHandle?: HTMLSpanElement;
-};
+export type NodeInstance<TreeDataType extends BasicDataNode = DataNode> =
+  React.Component<TreeNodeProps<TreeDataType>> & {
+    selectHandle?: HTMLSpanElement;
+  };
 
 export interface Entity {
   node: NodeElement;
@@ -58,10 +63,11 @@ export interface Entity {
   children?: Entity[];
 }
 
-export interface DataEntity extends Omit<Entity, 'node' | 'parent' | 'children'> {
-  node: DataNode;
-  parent?: DataEntity;
-  children?: DataEntity[];
+export interface DataEntity<TreeDataType extends BasicDataNode = DataNode>
+  extends Omit<Entity, 'node' | 'parent' | 'children'> {
+  node: TreeDataType;
+  parent?: DataEntity<TreeDataType>;
+  children?: DataEntity<TreeDataType>[];
   level: number;
 }
 
@@ -86,4 +92,4 @@ export interface FieldNames {
   title?: string;
   key?: string;
   children?: string;
-}
\ No newline at end of file
+}
diff --git a/src/util.tsx b/src/util.tsx
index 2b8e6c86..609fd023 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -14,6 +14,7 @@ import {
   NodeInstance,
   FlattenNode,
   Direction,
+  BasicDataNode,
 } from './interface';
 import { TreeProps, AllowDrop } from './Tree';
 
@@ -46,13 +47,16 @@ export function isTreeNode(node: NodeElement) {
   return node && node.type && node.type.isTreeNode;
 }
 
-export function getDragChildrenKeys(dragNodeKey: Key, keyEntities: Record<Key, DataEntity>): Key[] {
+export function getDragChildrenKeys<TreeDataType extends BasicDataNode = DataNode>(
+  dragNodeKey: Key,
+  keyEntities: Record<Key, DataEntity<TreeDataType>>,
+): Key[] {
   // not contains self
   // self for left or right drag
   const dragChildrenKeys = [];
 
   const entity = keyEntities[dragNodeKey];
-  function dig(list: DataEntity[] = []) {
+  function dig(list: DataEntity<TreeDataType>[] = []) {
     list.forEach(({ key, children }) => {
       dragChildrenKeys.push(key);
       dig(children);
@@ -64,7 +68,9 @@ export function getDragChildrenKeys(dragNodeKey: Key, keyEntities: Record<Key, D
   return dragChildrenKeys;
 }
 
-export function isLastChild(treeNodeEntity: DataEntity) {
+export function isLastChild<TreeDataType extends BasicDataNode = DataNode>(
+  treeNodeEntity: DataEntity<TreeDataType>,
+) {
   if (treeNodeEntity.parent) {
     const posArr = posToArr(treeNodeEntity.pos);
     return Number(posArr[posArr.length - 1]) === treeNodeEntity.parent.children.length - 1;
@@ -72,24 +78,26 @@ export function isLastChild(treeNodeEntity: DataEntity) {
   return false;
 }
 
-export function isFirstChild(treeNodeEntity: DataEntity) {
+export function isFirstChild<TreeDataType extends BasicDataNode = DataNode>(
+  treeNodeEntity: DataEntity<TreeDataType>,
+) {
   const posArr = posToArr(treeNodeEntity.pos);
   return Number(posArr[posArr.length - 1]) === 0;
 }
 
 // Only used when drag, not affect SSR.
-export function calcDropPosition(
+export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
   event: React.MouseEvent,
-  dragNode: NodeInstance,
-  targetNode: NodeInstance,
+  dragNode: NodeInstance<TreeDataType>,
+  targetNode: NodeInstance<TreeDataType>,
   indent: number,
   startMousePosition: {
     x: number;
     y: number;
   },
-  allowDrop: AllowDrop,
+  allowDrop: AllowDrop<TreeDataType>,
   flattenedNodes: FlattenNode[],
-  keyEntities: Record<Key, DataEntity>,
+  keyEntities: Record<Key, DataEntity<TreeDataType>>,
   expandKeys: Key[],
   direction: Direction,
 ): {
@@ -109,7 +117,7 @@ export function calcDropPosition(
   const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent;
 
   // find abstract drop node by horizontal offset
-  let abstractDropNodeEntity: DataEntity = keyEntities[targetNode.props.eventKey];
+  let abstractDropNodeEntity: DataEntity<TreeDataType> = keyEntities[targetNode.props.eventKey];
 
   if (clientY < top + height / 2) {
     // first half, set abstract drop node to previous node
@@ -345,17 +353,3 @@ export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, Dat
 
   return [...expandedKeys];
 }
-
-/**
- * Returns only the data- and aria- key/value pairs
- */
-export function getDataAndAria(props: Partial<TreeProps | TreeNodeProps>) {
-  const omitProps: Record<string, string> = {};
-  Object.keys(props).forEach(key => {
-    if (key.startsWith('data-') || key.startsWith('aria-')) {
-      omitProps[key] = props[key];
-    }
-  });
-
-  return omitProps;
-}
diff --git a/src/utils/conductUtil.ts b/src/utils/conductUtil.ts
index 8a523954..c5d8d3f3 100644
--- a/src/utils/conductUtil.ts
+++ b/src/utils/conductUtil.ts
@@ -1,5 +1,5 @@
 import warning from 'rc-util/lib/warning';
-import { Key, DataEntity, DataNode, GetCheckDisabled } from '../interface';
+import { Key, DataEntity, DataNode, GetCheckDisabled, BasicDataNode } from '../interface';
 
 interface ConductReturnType {
   checkedKeys: Key[];
@@ -16,17 +16,17 @@ function removeFromCheckedKeys(halfCheckedKeys: Set<Key>, checkedKeys: Set<Key>)
   return filteredKeys;
 }
 
-export function isCheckDisabled(node: DataNode) {
+export function isCheckDisabled<TreeDataType>(node: TreeDataType) {
   const { disabled, disableCheckbox, checkable } = (node || {}) as DataNode;
   return !!(disabled || disableCheckbox) || checkable === false;
 }
 
 // Fill miss keys
-function fillConductCheck(
+function fillConductCheck<TreeDataType extends BasicDataNode = DataNode>(
   keys: Set<Key>,
-  levelEntities: Map<number, Set<DataEntity>>,
+  levelEntities: Map<number, Set<DataEntity<TreeDataType>>>,
   maxLevel: number,
-  syntheticGetCheckDisabled: GetCheckDisabled<DataNode>,
+  syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
   const checkedKeys = new Set<Key>(keys);
   const halfCheckedKeys = new Set<Key>();
@@ -98,12 +98,12 @@ function fillConductCheck(
 }
 
 // Remove useless key
-function cleanConductCheck(
+function cleanConductCheck<TreeDataType extends BasicDataNode = DataNode>(
   keys: Set<Key>,
   halfKeys: Key[],
-  levelEntities: Map<number, Set<DataEntity>>,
+  levelEntities: Map<number, Set<DataEntity<TreeDataType>>>,
   maxLevel: number,
-  syntheticGetCheckDisabled: GetCheckDisabled<DataNode>,
+  syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
   const checkedKeys = new Set<Key>(keys);
   let halfCheckedKeys = new Set<Key>(halfKeys);
@@ -182,15 +182,15 @@ function cleanConductCheck(
  * @param keyEntities key - dataEntity map
  * @param mode `fill` to fill missing key, `clean` to remove useless key
  */
-export function conductCheck(
+export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
   keyList: Key[],
   checked: true | { checked: false; halfCheckedKeys: Key[] },
-  keyEntities: Record<Key, DataEntity>,
-  getCheckDisabled?: GetCheckDisabled<DataNode>,
+  keyEntities: Record<Key, DataEntity<TreeDataType>>,
+  getCheckDisabled?: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
   const warningMissKeys: Key[] = [];
 
-  let syntheticGetCheckDisabled: GetCheckDisabled<DataNode>;
+  let syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>;
   if (getCheckDisabled) {
     syntheticGetCheckDisabled = getCheckDisabled;
   } else {
@@ -208,7 +208,7 @@ export function conductCheck(
       return hasEntity;
     }),
   );
-  const levelEntities = new Map<number, Set<DataEntity>>();
+  const levelEntities = new Map<number, Set<DataEntity<TreeDataType>>>();
   let maxLevel = 0;
 
   // Convert entities by level for calculation
@@ -216,7 +216,7 @@ export function conductCheck(
     const entity = keyEntities[key];
     const { level } = entity;
 
-    let levelSet: Set<DataEntity> = levelEntities.get(level);
+    let levelSet: Set<DataEntity<TreeDataType>> = levelEntities.get(level);
     if (!levelSet) {
       levelSet = new Set();
       levelEntities.set(level, levelSet);
@@ -237,7 +237,12 @@ export function conductCheck(
 
   let result: ConductReturnType;
   if (checked === true) {
-    result = fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled);
+    result = fillConductCheck<TreeDataType>(
+      keys,
+      levelEntities,
+      maxLevel,
+      syntheticGetCheckDisabled,
+    );
   } else {
     result = cleanConductCheck(
       keys,
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 7e73fcad..14fc3d04 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -11,6 +11,7 @@ import {
   EventDataNode,
   GetKey,
   FieldNames,
+  BasicDataNode,
 } from '../interface';
 import { getPosition, isTreeNode } from '../util';
 import { TreeNodeProps } from '../TreeNode';
@@ -313,7 +314,7 @@ export function convertDataToEntities(
   return wrapper;
 }
 
-export interface TreeNodeRequiredProps {
+export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = DataNode> {
   expandedKeys: Key[];
   selectedKeys: Key[];
   loadedKeys: Key[];
@@ -322,13 +323,13 @@ export interface TreeNodeRequiredProps {
   halfCheckedKeys: Key[];
   dragOverNodeKey: Key;
   dropPosition: number;
-  keyEntities: Record<Key, DataEntity>;
+  keyEntities: Record<Key, DataEntity<TreeDataType>>;
 }
 
 /**
  * Get TreeNode props with Tree props.
  */
-export function getTreeNodeProps(
+export function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(
   key: Key,
   {
     expandedKeys,
@@ -340,7 +341,7 @@ export function getTreeNodeProps(
     dragOverNodeKey,
     dropPosition,
     keyEntities,
-  }: TreeNodeRequiredProps,
+  }: TreeNodeRequiredProps<TreeDataType>,
 ) {
   const entity = keyEntities[key];
 
@@ -365,7 +366,9 @@ export function getTreeNodeProps(
   return treeNodeProps;
 }
 
-export function convertNodePropsToEventData(props: TreeNodeProps): EventDataNode {
+export function convertNodePropsToEventData<TreeDataType extends BasicDataNode = DataNode>(
+  props: TreeNodeProps<TreeDataType>,
+): EventDataNode {
   const {
     data,
     expanded,
diff --git a/tests/type.spec.tsx b/tests/type.spec.tsx
new file mode 100644
index 00000000..6bf8f7fd
--- /dev/null
+++ b/tests/type.spec.tsx
@@ -0,0 +1,28 @@
+/* eslint-disable no-undef, react/no-multi-comp */
+import React from 'react';
+import { mount } from 'enzyme';
+import Tree, { BasicDataNode } from '../src';
+
+describe('Tree.TypeScript', () => {
+  it('fieldNames', () => {
+    interface DataType extends BasicDataNode {
+      label: string;
+      value: string;
+      list?: DataType[];
+    }
+
+    const wrapper = mount(
+      <Tree<DataType>
+        treeData={[
+          {
+            label: 'parent',
+            value: 'parent',
+            list: [],
+          },
+        ]}
+      />,
+    );
+
+    expect(wrapper).toBeTruthy();
+  });
+});
diff --git a/tests/util.spec.js b/tests/util.spec.js
index ca062e4f..63be0a09 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -6,7 +6,6 @@ import {
   convertDataToTree,
   conductExpandParent,
   getDragChildrenKeys,
-  getDataAndAria,
   parseCheckedKeys,
 } from '../src/util';
 import {
@@ -465,21 +464,6 @@ describe('Util', () => {
     expect(keys1.sort()).toEqual(['222', '333'].sort());
   });
 
-  describe('getDataAndAria', () => {
-    it('should return only data- and aria- properties', () => {
-      const props = {
-        'data-test': 'name',
-        'aria-label': 'name',
-        dataSource: '/api',
-        ariaLabel: 'some-label',
-      };
-      expect(getDataAndAria(props)).toEqual({
-        'data-test': 'name',
-        'aria-label': 'name',
-      });
-    });
-  });
-
   it('parseCheckedKeys warning', () => {
     const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
 

From a815967d3f8faaf1efa7353a0356df66e6350bbb Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 22 Nov 2021 15:52:10 +0800
Subject: [PATCH 218/397] 5.3.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b3f443f5..1b8871ba 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.2.2",
+  "version": "5.3.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 8fcb7138edc4172d0437cf9e163ea33b46f75c4d Mon Sep 17 00:00:00 2001
From: binyellow <571704908@qq.com>
Date: Mon, 22 Nov 2021 20:29:29 +0800
Subject: [PATCH 219/397] docs: update README.md (#504)

---
 README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index f0ca5b4e..1f78a5f3 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ see examples
 | checkStrictly | check node precisely, parent and children nodes are not associated | bool | false |
 | className | additional css class of root dom node | String | '' |
 | defaultCheckedKeys | default checked treeNodes | String[] | [] |
-| defaultExpandedKeys | expand specific treeNodes | String[] | - |
+| defaultExpandedKeys | expand specific treeNodes | String[] | [] |
 | defaultExpandAll | expand all treeNodes | bool | false |
 | defaultExpandParent | auto expand parent treeNodes when init | bool | true |
 | defaultSelectedKeys | default selected treeNodes | String[] | [] |
@@ -62,7 +62,7 @@ see examples
 | expandedKeys | Controlled expand specific treeNodes | String[] | - |
 | filterTreeNode | filter some treeNodes as you need. it should return true | function(node) | - |
 | icon | customize icon. When you pass component, whose render will receive full TreeNode props as component props | element/Function(props) | - |
-| loadedKeys | Mark node is loaded when `loadData` is true | string[] | - |
+| loadedKeys | Mark node is loaded when `loadData` is true | String[] | - |
 | loadData | load data asynchronously and the return value should be a promise | function(node) | - |
 | multiple | whether multiple select | bool | false |
 | prefixCls | prefix class | String | 'rc-tree' |
@@ -99,7 +99,7 @@ see examples
 | name | description | type | default |
 | --- | --- | --- | --- |
 | className | additional class to treeNode | String | '' |
-| checkable | control node checkable if Tree is checkable | bool | - |
+| checkable | control node checkable if Tree is checkable | bool | false |
 | style | set style to treeNode | Object | '' |
 | disabled | whether disabled the treeNode | bool | false |
 | disableCheckbox | whether disable the treeNode' checkbox | bool | false |

From c8225c6b6b56b83a6d390f421702728537441334 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 7 Dec 2021 18:28:57 +0800
Subject: [PATCH 220/397] chore: upgrade rc-util

close #509
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1b8871ba..6f43e79b 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,7 @@
     "@babel/runtime": "^7.10.1",
     "classnames": "2.x",
     "rc-motion": "^2.0.1",
-    "rc-util": "^5.0.0",
+    "rc-util": "^5.16.1",
     "rc-virtual-list": "^3.4.1"
   }
 }

From 09217a8239eab11741dc49366759c059a56b07a7 Mon Sep 17 00:00:00 2001
From: bregonesi <rego_1994@hotmail.com>
Date: Tue, 7 Dec 2021 07:31:37 -0300
Subject: [PATCH 221/397] fix(tree-type): fix generic type for onCheck (#510)

---
 src/Tree.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index d71756c6..24905209 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -131,7 +131,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   ) => void;
   onCheck?: (
     checked: { checked: Key[]; halfChecked: Key[] } | Key[],
-    info: CheckInfo<BasicDataNode>,
+    info: CheckInfo<TreeDataType>,
   ) => void;
   onSelect?: (
     selectedKeys: Key[],

From 3387cf16fbffd564c1d0de215c41a1bf25886ca4 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 9 Dec 2021 16:21:48 +0800
Subject: [PATCH 222/397] feat: Add internal mapping of fieldNames._title

---
 src/interface.tsx         | 11 +++++++----
 src/utils/treeUtil.ts     | 28 ++++++++++++++++++++++------
 tests/FieldNames.spec.tsx | 38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 67 insertions(+), 10 deletions(-)

diff --git a/src/interface.tsx b/src/interface.tsx
index 967652e9..6830a559 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -49,10 +49,11 @@ export type NodeElement = React.ReactElement<TreeNodeProps> & {
   };
 };
 
-export type NodeInstance<TreeDataType extends BasicDataNode = DataNode> =
-  React.Component<TreeNodeProps<TreeDataType>> & {
-    selectHandle?: HTMLSpanElement;
-  };
+export type NodeInstance<TreeDataType extends BasicDataNode = DataNode> = React.Component<
+  TreeNodeProps<TreeDataType>
+> & {
+  selectHandle?: HTMLSpanElement;
+};
 
 export interface Entity {
   node: NodeElement;
@@ -90,6 +91,8 @@ export type Direction = 'ltr' | 'rtl' | undefined;
 
 export interface FieldNames {
   title?: string;
+  /** @private Internal usage for `rc-tree-select`, safe to remove if no need */
+  _title?: string[];
   key?: string;
   children?: string;
 }
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 14fc3d04..8df8cbb7 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -23,11 +23,13 @@ export function getKey(key: Key, pos: string) {
   return pos;
 }
 
-export function fillFieldNames(fieldNames?: FieldNames) {
-  const { title, key, children } = fieldNames || {};
+export function fillFieldNames(fieldNames?: FieldNames): Required<FieldNames> {
+  const { title, _title, key, children } = fieldNames || {};
+  const mergedTitle = title || 'title';
 
   return {
-    title: title || 'title',
+    title: mergedTitle,
+    _title: _title || [mergedTitle],
     key: key || 'key',
     children: children || 'children',
   };
@@ -108,7 +110,11 @@ export function flattenTreeData(
   expandedKeys: Key[] | true,
   fieldNames: FieldNames,
 ): FlattenNode[] {
-  const { title: fieldTitle, key: fieldKey, children: fieldChildren } = fillFieldNames(fieldNames);
+  const {
+    _title: fieldTitles,
+    key: fieldKey,
+    children: fieldChildren,
+  } = fillFieldNames(fieldNames);
 
   const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys);
   const flattenList: FlattenNode[] = [];
@@ -118,10 +124,20 @@ export function flattenTreeData(
       const pos: string = getPosition(parent ? parent.pos : '0', index);
       const mergedKey = getKey(treeNode[fieldKey], pos);
 
+      // Pick matched title in field title list
+      let mergedTitle: React.ReactNode;
+      for (let i = 0; i < fieldTitles.length; i += 1) {
+        const fieldTitle = fieldTitles[i];
+        if (treeNode[fieldTitle]) {
+          mergedTitle = treeNode[fieldTitle];
+          break;
+        }
+      }
+
       // Add FlattenDataNode into list
       const flattenNode: FlattenNode = {
-        ...omit(treeNode, [fieldTitle, fieldKey, fieldChildren] as any),
-        title: treeNode[fieldTitle],
+        ...omit(treeNode, [...fieldTitles, fieldKey, fieldChildren] as any),
+        title: mergedTitle,
         key: mergedKey,
         parent,
         pos,
diff --git a/tests/FieldNames.spec.tsx b/tests/FieldNames.spec.tsx
index 0b3997e8..ce4aa3df 100644
--- a/tests/FieldNames.spec.tsx
+++ b/tests/FieldNames.spec.tsx
@@ -139,4 +139,42 @@ describe('FieldNames', () => {
     wrapper.find('.rc-tree-checkbox').last().simulate('click');
     expect(onCheck).toHaveBeenCalledWith(['parent', 'child'], expect.anything());
   });
+
+  // Internal usage. Safe to remove
+  it('fieldNames using _title', () => {
+    const wrapper = mount(
+      <Tree
+        treeData={
+          [
+            {
+              myTitle: 'Title',
+              myKey: 'title',
+              myChildren: [
+                { myTitle: 'Sub1', myKey: 'sub_1' },
+                { myTitle: 'Sub2', myKey: 'sub_2' },
+              ],
+            },
+          ] as any
+        }
+        defaultExpandAll
+        fieldNames={{
+          _title: ['notExist', 'myTitle'],
+          key: 'myKey',
+          children: 'myChildren',
+        }}
+      />,
+    );
+
+    // Title
+    const titleList = wrapper
+      .find('.rc-tree-list-holder div.rc-tree-treenode')
+      .map(node => node.text());
+
+    expect(titleList).toEqual(['Title', 'Sub1', 'Sub2']);
+
+    // Key
+    const keyList = wrapper.find(InternalTreeNode).map(node => node.props().eventKey);
+
+    expect(keyList).toEqual(['title', 'sub_1', 'sub_2']);
+  });
 });

From 48c35637881ba901fa0d80d2a9df6df767df8c95 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 9 Dec 2021 16:22:43 +0800
Subject: [PATCH 223/397] 5.3.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6f43e79b..99bb894d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.3.0",
+  "version": "5.3.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From b88dc076375d44616573251edbd186b04187f44e Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 9 Dec 2021 16:28:27 +0800
Subject: [PATCH 224/397] fix: fieldNames should support 0

---
 src/utils/treeUtil.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 8df8cbb7..7fc2026a 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -128,7 +128,7 @@ export function flattenTreeData(
       let mergedTitle: React.ReactNode;
       for (let i = 0; i < fieldTitles.length; i += 1) {
         const fieldTitle = fieldTitles[i];
-        if (treeNode[fieldTitle]) {
+        if (treeNode[fieldTitle] !== undefined) {
           mergedTitle = treeNode[fieldTitle];
           break;
         }

From ba4aa47c9e1f205985a20430771910c88489b759 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 9 Dec 2021 16:28:59 +0800
Subject: [PATCH 225/397] 5.3.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 99bb894d..1e9407f6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.3.1",
+  "version": "5.3.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 3fe4a936e309776e47279fe7287b67855761843a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 14 Dec 2021 15:23:29 +0800
Subject: [PATCH 226/397] fix: Keyboard should work with `fieldNames` of
 renaming `key` field (#514)

* fix: Key board should work with fieldNames

* fix: active key check logic
---
 src/Tree.tsx                |  6 +++---
 tests/Accessibility.spec.js | 41 ++++++++++++++++++++++++++-----------
 2 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 24905209..d3146217 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -1174,13 +1174,13 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       return null;
     }
 
-    return flattenNodes.find(({ data: { key } }) => key === activeKey) || null;
+    return flattenNodes.find(({ key }) => key === activeKey) || null;
   };
 
   offsetActiveKey = (offset: number) => {
     const { flattenNodes, activeKey } = this.state;
 
-    let index = flattenNodes.findIndex(({ data: { key } }) => key === activeKey);
+    let index = flattenNodes.findIndex(({ key }) => key === activeKey);
 
     // Align with index
     if (index === -1 && offset < 0) {
@@ -1191,7 +1191,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
     const item = flattenNodes[index];
     if (item) {
-      const { key } = item.data;
+      const { key } = item;
       this.onActiveChange(key);
     } else {
       this.onActiveChange(null);
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index efcb4d4e..f42d011c 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -145,17 +145,11 @@ describe('Tree Accessibility', () => {
       expect(onBlur).toHaveBeenCalled();
 
       // null activeKey
-      wrapper
-        .find('.rc-tree-treenode')
-        .first()
-        .simulate('mouseMove');
+      wrapper.find('.rc-tree-treenode').first().simulate('mouseMove');
       checkActiveTrigger(null);
 
       for (let i = 0; i < 10; i += 1) {
-        wrapper
-          .find('.rc-tree-treenode')
-          .first()
-          .simulate('mouseMove');
+        wrapper.find('.rc-tree-treenode').first().simulate('mouseMove');
         expect(onActiveChange).not.toHaveBeenCalled();
       }
     }
@@ -207,12 +201,35 @@ describe('Tree Accessibility', () => {
       expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeTruthy();
 
       // Mouse move
-      wrapper
-        .find('.rc-tree-treenode')
-        .at(1)
-        .simulate('mouseMove');
+      wrapper.find('.rc-tree-treenode').at(1).simulate('mouseMove');
       expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeFalsy();
     });
+
+    it('fieldNames should also work', () => {
+      const onActiveChange = jest.fn();
+      const onSelect = jest.fn();
+
+      const wrapper = mount(
+        <Tree
+          defaultExpandAll
+          treeData={[{ value: 'first' }, { value: 'second' }]}
+          fieldNames={{ key: 'value' }}
+          onActiveChange={onActiveChange}
+          onSelect={onSelect}
+        />,
+      );
+
+      wrapper.find('input').simulate('focus');
+
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
+      expect(onActiveChange).toHaveBeenCalledWith('first');
+
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
+      expect(onActiveChange).toHaveBeenCalledWith('second');
+
+      wrapper.find('input').simulate('keyDown', { which: KeyCode.ENTER });
+      expect(onSelect).toHaveBeenCalledWith(['second'], expect.anything());
+    });
   });
 
   it('disabled should prevent keyboard', () => {

From 6cdc2efb4761aa47fee5dc26184d8f14bafe439a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 14 Dec 2021 15:23:59 +0800
Subject: [PATCH 227/397] 5.3.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1e9407f6..6a9a1962 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.3.2",
+  "version": "5.3.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From b9328631a8e0db62f49b887c8e6d66d92b134dbb Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 16 Dec 2021 11:57:52 +0800
Subject: [PATCH 228/397] chore: Internal convertDataToEntities provides nodes
 as path

---
 src/interface.tsx     |  1 +
 src/utils/treeUtil.ts | 23 ++++++++++++++++-------
 tests/util.spec.js    | 11 ++++++++++-
 3 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/src/interface.tsx b/src/interface.tsx
index 6830a559..c1ef7b7f 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -67,6 +67,7 @@ export interface Entity {
 export interface DataEntity<TreeDataType extends BasicDataNode = DataNode>
   extends Omit<Entity, 'node' | 'parent' | 'children'> {
   node: TreeDataType;
+  nodes: TreeDataType[];
   parent?: DataEntity<TreeDataType>;
   children?: DataEntity<TreeDataType>[];
   level: number;
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 7fc2026a..289df048 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -186,6 +186,7 @@ export function traverseDataNodes(
     key: Key;
     parentPos: string | number;
     level: number;
+    nodes: DataNode[];
   }) => void,
   // To avoid too many params, let use config instead of origin param
   config?: TraverseDataNodesConfig | string,
@@ -222,9 +223,11 @@ export function traverseDataNodes(
     node: DataNode,
     index?: number,
     parent?: { node: DataNode; pos: string; level: number },
+    pathNodes?: DataNode[],
   ) {
     const children = node ? node[mergeChildrenPropName] : dataNodes;
     const pos = node ? getPosition(parent.pos, index) : '0';
+    const connectNodes = node ? [...pathNodes, node] : [];
 
     // Process node if is not root
     if (node) {
@@ -236,6 +239,7 @@ export function traverseDataNodes(
         key,
         parentPos: parent.node ? parent.pos : null,
         level: parent.level + 1,
+        nodes: connectNodes,
       };
 
       callback(data);
@@ -244,11 +248,16 @@ export function traverseDataNodes(
     // Process children node
     if (children) {
       children.forEach((subNode, subIndex) => {
-        processNode(subNode, subIndex, {
-          node,
-          pos,
-          level: parent ? parent.level + 1 : -1,
-        });
+        processNode(
+          subNode,
+          subIndex,
+          {
+            node,
+            pos,
+            level: parent ? parent.level + 1 : -1,
+          },
+          connectNodes,
+        );
       });
     }
   }
@@ -301,8 +310,8 @@ export function convertDataToEntities(
   traverseDataNodes(
     dataNodes,
     item => {
-      const { node, index, pos, key, parentPos, level } = item;
-      const entity: DataEntity = { node, index, key, pos, level };
+      const { node, index, pos, key, parentPos, level, nodes } = item;
+      const entity: DataEntity = { node, nodes, index, key, pos, level };
 
       const mergedKey = getKey(key, pos);
 
diff --git a/tests/util.spec.js b/tests/util.spec.js
index 63be0a09..072bd965 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -113,11 +113,20 @@ describe('Util', () => {
   });
 
   describe('convertDataToEntities', () => {
-    it('basic', () => {
+    it('basic convert', () => {
       const entities = convertDataToEntities([
         { key: 'parent', children: [{ key: 0 }, { key: 1 }] },
       ]);
       expect(Object.keys(entities.keyEntities).sort()).toEqual(['0', '1', 'parent']);
+
+      // Used for `rc-cascader` to get fully path
+      expect(entities.keyEntities.parent.nodes).toEqual([
+        expect.objectContaining({ key: 'parent' }),
+      ]);
+      expect(entities.keyEntities[0].nodes).toEqual([
+        expect.objectContaining({ key: 'parent' }),
+        expect.objectContaining({ key: 0 }),
+      ]);
     });
 
     it('with string externalGetKey', () => {

From 0eacb98053363425e6f7c0ec50bd3297e7778059 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 16 Dec 2021 11:59:32 +0800
Subject: [PATCH 229/397] chore: fix compile

---
 src/NodeList.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 481eab95..3983bcb5 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -34,6 +34,7 @@ export const MotionEntity: DataEntity = {
   index: 0,
   pos: '0',
   node: MotionNode,
+  nodes: [MotionNode],
 };
 
 const MotionFlattenData: FlattenNode = {

From a685916c1079b3b538e94ea24e0bf3d35aa6842f Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Thu, 16 Dec 2021 11:59:58 +0800
Subject: [PATCH 230/397] 5.3.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6a9a1962..f9449121 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.3.3",
+  "version": "5.3.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 93649d9d08a24dfe6aa34ce3ed0abf79db47f3e4 Mon Sep 17 00:00:00 2001
From: James <daoxingyue@live.cn>
Date: Mon, 20 Dec 2021 11:33:19 +0800
Subject: [PATCH 231/397] feature: user can  customized  tree node style if
 selectable is false (#487)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feature: user can  customized  tree node style if selectable.

* feat: Tree support drag handler (#490)

* refactor: Move code into unique checker

* chore: support handler

* refactor: Move drag out

* chore: Patch more style info

* test: Fix test case

* docs: Update demo

* chore: Coverage

* 5.1.0

* refactor: Merge draggable api (#491)

* 5.2.0

* chore: Retry 10 times after failed (#493)

* chore: add max retry ant-design/ant-design#32313

* test: test case

* 5.2.1

* fix: field names failed for checkable (#498)

* fix: fieldNames checked logic

* test: test case

* 5.2.2

* merge

* 5.3.0

* docs: update README.md (#504)

* chore: upgrade rc-util

close #509

* fix(tree-type): fix generic type for onCheck (#510)

* feat: Add internal mapping of fieldNames._title

* 5.3.1

* fix: fieldNames should support 0

* 5.3.2

* resolve confilicts for rebase

* add demo and test case for this feature

* remove unnecessary code

* resolve confilicts for rebase

* add aria-selected prop and update snapshot

* use aria-selected instead unseleactable prop ,also update demo and test case

* add missing code

* change aria-selected prop,just show it when prop exists

Co-authored-by: 二货机器人 <smith3816@gmail.com>
Co-authored-by: binyellow <571704908@qq.com>
Co-authored-by: afc163 <afc163@gmail.com>
Co-authored-by: bregonesi <rego_1994@hotmail.com>
---
 docs/demo/selectable.md       |  3 ++
 docs/examples/selectable.jsx  | 94 +++++++++++++++++++++++++++++++++++
 docs/examples/selectable.less | 14 ++++++
 src/TreeNode.tsx              |  3 ++
 tests/TreeNodeProps.spec.js   | 48 ++++++++++++++++++
 5 files changed, 162 insertions(+)
 create mode 100644 docs/demo/selectable.md
 create mode 100644 docs/examples/selectable.jsx
 create mode 100644 docs/examples/selectable.less

diff --git a/docs/demo/selectable.md b/docs/demo/selectable.md
new file mode 100644
index 00000000..387ee44b
--- /dev/null
+++ b/docs/demo/selectable.md
@@ -0,0 +1,3 @@
+## selectable
+
+<code src="../examples/selectable.jsx">
diff --git a/docs/examples/selectable.jsx b/docs/examples/selectable.jsx
new file mode 100644
index 00000000..e45dc833
--- /dev/null
+++ b/docs/examples/selectable.jsx
@@ -0,0 +1,94 @@
+/* eslint-disable no-console, react/no-access-state-in-setstate */
+import React from 'react';
+import './selectable.less';
+import '../../assets/index.less';
+import Tree, { TreeNode } from 'rc-tree';
+
+class Demo extends React.Component {
+
+  render() {
+    return (
+      <div className="selectable-demo">
+        <h2>selectable</h2>
+        <p>normal</p>
+        <div>
+          <Tree defaultExpandAll showLine>
+            <TreeNode title="root node" key="0-0">
+              <TreeNode title="parent 1 default value(true)" key="0-0-0">
+                <TreeNode
+                  title="use parent 1 value if dont set selectable obviously"
+                  key="0-0-0-0"
+                />
+                <TreeNode selectable={true} title="set selectable is true" key="0-0-0-1" />
+              </TreeNode>
+              <TreeNode title="parent 2 selectable is false" selectable={false} key="0-0-1">
+                <TreeNode
+                  selectable={false}
+                  title="use parent 2 value if dont set selectable obviously"
+                  key="0-0-1-0"
+                />
+                <TreeNode
+                  selectable={true}
+                  title="if set selectable obviously, it does't affect by parent"
+                  key="0-0-1-1"
+                />
+              </TreeNode>
+              <TreeNode title="parent 3 disabled" key="0-0-2" disabled>
+                <TreeNode
+                  selectable={false}
+                  title="parent is disable ,and selectable is false"
+                  key="0-0-2-0"
+                />
+                <TreeNode
+                  selectable={true}
+                  title="parent is disable ,and selectable is true"
+                  key="0-0-2-1"
+                />
+              </TreeNode>
+            </TreeNode>
+          </Tree>
+        </div>
+        <p>customized tree node style if unselectable</p>
+        <div className="selectable-container">
+          <Tree prefixCls="rc-tree" defaultExpandAll showLine>
+            <TreeNode title="root node" key="0-0">
+              <TreeNode title="parent 1 default value(true)" key="0-0-0">
+                <TreeNode
+                  title="use parent 1 value if dont set selectable obviously"
+                  key="0-0-0-0"
+                />
+                <TreeNode selectable={true} title="set selectable is true" key="0-0-0-1" />
+              </TreeNode>
+              <TreeNode title="parent 2 selectable is false" selectable={false} key="0-0-1">
+                <TreeNode
+                  selectable={false}
+                  title="use parent 2 value if dont set selectable obviously"
+                  key="0-0-1-0"
+                />
+                <TreeNode
+                  selectable={true}
+                  title="if set selectable obviously, it does't affect by parent"
+                  key="0-0-1-1"
+                />
+              </TreeNode>
+              <TreeNode title="parent 3 disabled" key="0-0-2" disabled>
+                <TreeNode
+                  selectable={false}
+                  title="parent is disable ,and selectable is false"
+                  key="0-0-2-0"
+                />
+                <TreeNode
+                  selectable={true}
+                  title="parent is disable ,and selectable is true"
+                  key="0-0-2-1"
+                />
+              </TreeNode>
+            </TreeNode>
+          </Tree>
+        </div>
+      </div>
+    );
+  }
+}
+
+export default Demo;
diff --git a/docs/examples/selectable.less b/docs/examples/selectable.less
new file mode 100644
index 00000000..fba83940
--- /dev/null
+++ b/docs/examples/selectable.less
@@ -0,0 +1,14 @@
+@import '../../assets/index.less';
+.selectable-demo {
+  padding: 0 20px;
+  .selectable-container {
+    margin: 10px 30px;
+    overflow: auto;
+    border: 1px solid #ccc;
+    .@{treeNodePrefixCls}[aria-selected="false"] {
+      .@{treePrefixCls}-node-content-wrapper span:last-child {
+        text-decoration: line-through;
+      }
+    }
+  }
+}
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 17d244fa..82fdb6f4 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -540,6 +540,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       active,
       data,
       onMouseMove,
+      selectable,
       ...otherProps
     } = this.props;
     const {
@@ -561,6 +562,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const draggableWithoutDisabled = !disabled && mergedDraggable;
 
     const dragging = draggingNodeKey === eventKey;
+    const ariaSelected = selectable !== undefined ? { 'aria-selected': !!selectable } : undefined;
 
     return (
       <div
@@ -596,6 +598,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
         onDrop={mergedDraggable ? this.onDrop : undefined}
         onDragEnd={mergedDraggable ? this.onDragEnd : undefined}
         onMouseMove={onMouseMove}
+        {...ariaSelected}
         {...dataOrAriaAttributeProps}
       >
         <Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
diff --git a/tests/TreeNodeProps.spec.js b/tests/TreeNodeProps.spec.js
index c9ba542d..681025ff 100644
--- a/tests/TreeNodeProps.spec.js
+++ b/tests/TreeNodeProps.spec.js
@@ -166,4 +166,52 @@ describe('TreeNode Props', () => {
     expect(onClick).toHaveBeenCalled();
     expect(onSelect).not.toHaveBeenCalled();
   });
+
+  it('unselectable', () => {
+    const onClick = jest.fn();
+    const onSelect = jest.fn();
+
+    class Demo extends React.Component {
+      state = {
+        treeSelectable: false,
+        treeNodeSelectable: false,
+      };
+
+      render() {
+        return (
+          <div>
+            <Tree selectable={this.state.treeSelectable} onClick={onClick} onSelect={onSelect}>
+              <TreeNode />
+              <TreeNode selectable={this.state.treeNodeSelectable} />
+            </Tree>
+            <button
+              className="test-button"
+              onClick={() => {
+                this.setState({
+                  treeSelectable: true,
+                  treeNodeSelectable: false,
+                });
+              }}
+            />
+          </div>
+        );
+      }
+    }
+
+    const wrapper = mount(<Demo />);
+    // tree selectable is false ,then children should be selectable = false if not set selectable alone.
+    expect(wrapper.find('[aria-selected=false]').length).toBe(1);
+    wrapper.find('.rc-tree-node-content-wrapper').at(1).simulate('click');
+    expect(onClick).toHaveBeenCalled();
+    expect(onSelect).not.toHaveBeenCalled();
+
+    // only set tree node use state.
+    wrapper.find('.test-button').simulate('click');
+    onClick.mockRestore();
+    onSelect.mockRestore();
+    expect(wrapper.find('[aria-selected=false]').length).toBe(1);
+    wrapper.find('.rc-tree-node-content-wrapper').at(1).simulate('click');
+    expect(onClick).toHaveBeenCalled();
+    expect(onSelect).not.toHaveBeenCalled();
+  });
 });

From f4bd3859771499534597023cc9ca6b56a1456d8d Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 20 Dec 2021 11:34:50 +0800
Subject: [PATCH 232/397] 5.3.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index f9449121..3c63b663 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.3.4",
+  "version": "5.3.5",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From f9f55dec40dd14216434f8e47539c9289eeccc71 Mon Sep 17 00:00:00 2001
From: Haniel Cui <coldice945@hotmail.com>
Date: Thu, 23 Dec 2021 22:37:10 +0800
Subject: [PATCH 233/397] fix: If set selectedKeys or expandedKeys to
 undefined, will be error (#492)

Enhanced code robustness
---
 src/util.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/util.tsx b/src/util.tsx
index 609fd023..c1ce4b60 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -19,6 +19,7 @@ import {
 import { TreeProps, AllowDrop } from './Tree';
 
 export function arrDel(list: Key[], value: Key) {
+  if (!list) return [];
   const clone = list.slice();
   const index = clone.indexOf(value);
   if (index >= 0) {
@@ -28,7 +29,7 @@ export function arrDel(list: Key[], value: Key) {
 }
 
 export function arrAdd(list: Key[], value: Key) {
-  const clone = list.slice();
+  const clone = (list || []).slice();
   if (clone.indexOf(value) === -1) {
     clone.push(value);
   }

From 3fffec85736a6424d5f2fa1b9a4df951fe7a4363 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Thu, 23 Dec 2021 22:40:09 +0800
Subject: [PATCH 234/397] 5.3.6

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 3c63b663..d6ba676d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.3.5",
+  "version": "5.3.6",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From e6af6af0cab9224a12783654f58323918263ca47 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Fri, 24 Dec 2021 17:18:09 +0800
Subject: [PATCH 235/397] fix: Elements outside tree shouldn't trigger drag
 event (#516)

* fix: Elements outside tree shouldn't trigger drag event

* test: simplify test case for dragging

* refactor: replace duplicated setState with new function resetDragState

* fix: recover wrong refactor
---
 docs/examples/draggable.jsx |  1 +
 src/Tree.tsx                | 64 +++++++++++++++----------------------
 tests/TreeDraggable.spec.js | 19 +++++++++++
 3 files changed, 46 insertions(+), 38 deletions(-)

diff --git a/docs/examples/draggable.jsx b/docs/examples/draggable.jsx
index a020478a..5809e598 100644
--- a/docs/examples/draggable.jsx
+++ b/docs/examples/draggable.jsx
@@ -122,6 +122,7 @@ class Demo extends React.Component {
             itemHeight={20}
             virtual={false}
           />
+          <div draggable>This element is draggable, but it cannot be dragged into tree.</div>
         </div>
       </div>
     );
diff --git a/src/Tree.tsx b/src/Tree.tsx
index d3146217..16bbafe7 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -479,6 +479,11 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       this.currentMouseOverDroppableNodeKey = eventKey;
     }
 
+    if (!dragNode) {
+      this.resetDragState();
+      return;
+    }
+
     const {
       dropPosition,
       dropLevelOffset,
@@ -501,21 +506,12 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     );
 
     if (
-      !dragNode ||
       // don't allow drop inside its children
       dragChildrenKeys.indexOf(dropTargetKey) !== -1 ||
       // don't allow drop when drop is not allowed caculated by calcDropPosition
       !dropAllowed
     ) {
-      this.setState({
-        dragOverNodeKey: null,
-        dropPosition: null,
-        dropLevelOffset: null,
-        dropTargetKey: null,
-        dropContainerKey: null,
-        dropTargetPos: null,
-        dropAllowed: false,
-      });
+      this.resetDragState();
       return;
     }
 
@@ -559,15 +555,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
     // Skip if drag node is self
     if (dragNode.props.eventKey === dropTargetKey && dropLevelOffset === 0) {
-      this.setState({
-        dragOverNodeKey: null,
-        dropPosition: null,
-        dropLevelOffset: null,
-        dropTargetKey: null,
-        dropContainerKey: null,
-        dropTargetPos: null,
-        dropAllowed: false,
-      });
+      this.resetDragState();
       return;
     }
 
@@ -596,6 +584,10 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     const { onDragOver, allowDrop, direction } = this.props;
     const { dragNode } = this;
 
+    if (!dragNode) {
+      return;
+    }
+
     const {
       dropPosition,
       dropLevelOffset,
@@ -617,7 +609,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       direction,
     );
 
-    if (!dragNode || dragChildrenKeys.indexOf(dropTargetKey) !== -1 || !dropAllowed) {
+    if (dragChildrenKeys.indexOf(dropTargetKey) !== -1 || !dropAllowed) {
       // don't allow drop inside its children
       // don't allow drop when drop is not allowed caculated by calcDropPosition
       return;
@@ -637,15 +629,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
           this.state.dragOverNodeKey === null
         )
       ) {
-        this.setState({
-          dropPosition: null,
-          dropLevelOffset: null,
-          dropTargetKey: null,
-          dropContainerKey: null,
-          dropTargetPos: null,
-          dropAllowed: false,
-          dragOverNodeKey: null,
-        });
+        this.resetDragState();
       }
     } else if (
       !(
@@ -681,15 +665,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       this.currentMouseOverDroppableNodeKey === node.props.eventKey &&
       !event.currentTarget.contains(event.relatedTarget as Node)
     ) {
-      this.setState({
-        dropPosition: null,
-        dropLevelOffset: null,
-        dropTargetKey: null,
-        dropContainerKey: null,
-        dropTargetPos: null,
-        dropAllowed: false,
-        dragOverNodeKey: null,
-      });
+      this.resetDragState();
       this.currentMouseOverDroppableNodeKey = null;
     }
 
@@ -768,6 +744,18 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     this.dragNode = null;
   };
 
+  resetDragState() {
+    this.setState({
+      dragOverNodeKey: null,
+      dropPosition: null,
+      dropLevelOffset: null,
+      dropTargetKey: null,
+      dropContainerKey: null,
+      dropTargetPos: null,
+      dropAllowed: false,
+    });
+  }
+
   cleanDragState = () => {
     const { draggingNodeKey } = this.state;
     if (draggingNodeKey !== null) {
diff --git a/tests/TreeDraggable.spec.js b/tests/TreeDraggable.spec.js
index 9b26e4da..f86a3709 100644
--- a/tests/TreeDraggable.spec.js
+++ b/tests/TreeDraggable.spec.js
@@ -708,6 +708,25 @@ describe('Tree Draggable', () => {
         expect(allowDrop.mock.calls[0][0].dragNode.key).toEqual('0-0');
         expect(allowDrop.mock.calls[0][0].dropNode.key).toEqual('0-1-0-0');
       });
+
+      it('not allow dragging elements outside into tree', () => {
+        const onDrop = jest.fn();
+        const wrapper = mount(
+          <div>
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0">
+                <TreeNode key="0-0-0" className="dropTarget" />
+              </TreeNode>
+            </Tree>
+            <div className="dragTarget">Element outside</div>
+          </div>,
+        );
+        wrapper.find('.dragTarget').simulate('dragStart');
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
+        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        expect(onDrop).not.toHaveBeenCalled();
+      });
     });
   });
 

From f779c52afcafbf937632e6b8100478b87124d254 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Mon, 27 Dec 2021 11:48:33 +0800
Subject: [PATCH 236/397] v5.3.7

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d6ba676d..98f2d7a5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.3.6",
+  "version": "5.3.7",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 193936d1ab0c920f720072b595bca12a22c01fa4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 10 Jan 2022 14:11:41 +0800
Subject: [PATCH 237/397] fix: draggable not support field names (#521)

* fix: Drag not break with fieldNames

* docs: back of demo
---
 src/util.tsx                |  4 ++--
 tests/TreeDraggable.spec.js | 41 +++++++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/src/util.tsx b/src/util.tsx
index c1ce4b60..5e9d4e33 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -123,10 +123,10 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
   if (clientY < top + height / 2) {
     // first half, set abstract drop node to previous node
     const nodeIndex = flattenedNodes.findIndex(
-      flattenedNode => flattenedNode.data.key === abstractDropNodeEntity.key,
+      flattenedNode => flattenedNode.key === abstractDropNodeEntity.key,
     );
     const prevNodeIndex = nodeIndex <= 0 ? 0 : nodeIndex - 1;
-    const prevNodeKey = flattenedNodes[prevNodeIndex].data.key;
+    const prevNodeKey = flattenedNodes[prevNodeIndex].key;
     abstractDropNodeEntity = keyEntities[prevNodeKey];
   }
 
diff --git a/tests/TreeDraggable.spec.js b/tests/TreeDraggable.spec.js
index f86a3709..a4558adf 100644
--- a/tests/TreeDraggable.spec.js
+++ b/tests/TreeDraggable.spec.js
@@ -754,4 +754,45 @@ describe('Tree Draggable', () => {
 
     expect(wrapper.find('.handler')).toHaveLength(2);
   });
+
+  it('not break with fieldNames', () => {
+    const onDrop = jest.fn();
+
+    const wrapper = mount(
+      <Tree
+        draggable
+        defaultExpandAll
+        onDrop={onDrop}
+        treeData={[
+          {
+            name: 'parent',
+            childList: [
+              {
+                className: 'dragTarget',
+                name: 'child 1',
+              },
+              {
+                className: 'dropTarget',
+                name: 'child 2',
+              },
+            ],
+          },
+        ]}
+        fieldNames={{
+          children: 'childList',
+          title: 'name',
+          key: 'name',
+        }}
+      />,
+    );
+
+    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
+    wrapper.find('.dropTarget').at(0).simulate('dragEnter', {
+      clientY: -9999999,
+    });
+    wrapper.find('.dropTarget').at(0).simulate('dragOver');
+    wrapper.find('.dropTarget').at(0).simulate('drop');
+
+    expect(onDrop).toHaveBeenCalled();
+  });
 });

From 531cdce0b9e7d926a0ece6f294e7a5574184fe5d Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Mon, 10 Jan 2022 14:12:24 +0800
Subject: [PATCH 238/397] 5.3.8

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 98f2d7a5..e505ff8c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.3.7",
+  "version": "5.3.8",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 332edac5983d0a4b63ba70639189872a19d0d097 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 18 Jan 2022 14:57:45 +0800
Subject: [PATCH 239/397] feat: Support activeKey (#525)

---
 src/Tree.tsx                |  6 ++++++
 tests/Accessibility.spec.js | 18 ++++++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 16bbafe7..eb26af1e 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -83,6 +83,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   className?: string;
   style?: React.CSSProperties;
   focusable?: boolean;
+  activeKey?: Key;
   tabIndex?: number;
   children?: React.ReactNode;
   treeData?: TreeDataType[]; // Generate treeNode by children
@@ -430,6 +431,11 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       newState.loadedKeys = props.loadedKeys;
     }
 
+    // ================== activeKey ==================
+    if (needSync('activeKey')) {
+      newState.activeKey = props.activeKey;
+    }
+
     return newState;
   }
 
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index f42d011c..a61966fb 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -236,4 +236,22 @@ describe('Tree Accessibility', () => {
     const wrapper = mount(<Tree disabled />);
     expect(wrapper.find('input').props().disabled).toBeTruthy();
   });
+
+  it('activeKey in control', () => {
+    const wrapper = mount(
+      <Tree
+        treeData={[
+          {
+            title: 'Parent',
+            key: 'parent',
+          },
+        ]}
+      />,
+    );
+
+    expect(wrapper.exists('.rc-tree-treenode-active')).toBeFalsy();
+
+    wrapper.setProps({ activeKey: 'parent' });
+    expect(wrapper.exists('.rc-tree-treenode-active')).toBeTruthy();
+  });
 });

From 009f8c95798c5fead0d8195ac09599447dc67b47 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 18 Jan 2022 14:58:44 +0800
Subject: [PATCH 240/397] 5.4.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index e505ff8c..06e864d5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.3.8",
+  "version": "5.4.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 8977c56d7a50f398c51942d28bf2f9c2648e2656 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 18 Jan 2022 15:33:37 +0800
Subject: [PATCH 241/397] fix: activeKey should support fieldNames (#526)

---
 src/NodeList.tsx            |  2 +-
 tests/Accessibility.spec.js | 53 ++++++++++++++++++++++++++-----------
 2 files changed, 38 insertions(+), 17 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 3983bcb5..8b5bbbf5 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -341,7 +341,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
               {...restProps}
               {...treeNodeProps}
               title={title}
-              active={!!activeItem && key === activeItem.data.key}
+              active={!!activeItem && key === activeItem.key}
               pos={pos}
               data={treeNode.data}
               isStart={isStart}
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index a61966fb..4ade59b2 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -237,21 +237,42 @@ describe('Tree Accessibility', () => {
     expect(wrapper.find('input').props().disabled).toBeTruthy();
   });
 
-  it('activeKey in control', () => {
-    const wrapper = mount(
-      <Tree
-        treeData={[
-          {
-            title: 'Parent',
-            key: 'parent',
-          },
-        ]}
-      />,
-    );
-
-    expect(wrapper.exists('.rc-tree-treenode-active')).toBeFalsy();
-
-    wrapper.setProps({ activeKey: 'parent' });
-    expect(wrapper.exists('.rc-tree-treenode-active')).toBeTruthy();
+  describe('activeKey in control', () => {
+    it('basic', () => {
+      const wrapper = mount(
+        <Tree
+          treeData={[
+            {
+              title: 'Parent',
+              key: 'parent',
+            },
+          ]}
+        />,
+      );
+
+      expect(wrapper.exists('.rc-tree-treenode-active')).toBeFalsy();
+
+      wrapper.setProps({ activeKey: 'parent' });
+      expect(wrapper.exists('.rc-tree-treenode-active')).toBeTruthy();
+    });
+
+    it('with fieldNames', () => {
+      const wrapper = mount(
+        <Tree
+          fieldNames={{ key: 'value' }}
+          treeData={[
+            {
+              title: 'Parent',
+              value: 'parent',
+            },
+          ]}
+        />,
+      );
+
+      expect(wrapper.exists('.rc-tree-treenode-active')).toBeFalsy();
+
+      wrapper.setProps({ activeKey: 'parent' });
+      expect(wrapper.exists('.rc-tree-treenode-active')).toBeTruthy();
+    });
   });
 });

From 38ef2ebe43972216ab916e8759d4a881ebe66311 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 18 Jan 2022 15:34:18 +0800
Subject: [PATCH 242/397] 5.4.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 06e864d5..766d9b45 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.4.0",
+  "version": "5.4.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 054137a7a813facc5b2d7d72b36face4c50d3b23 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 18 Jan 2022 16:16:57 +0800
Subject: [PATCH 243/397] chore: move sync scroll out of onActiveChange

---
 src/Tree.tsx | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index eb26af1e..b998f913 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -307,6 +307,17 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
   listRef = React.createRef<NodeListRef>();
 
+  componentDidUpdate(
+    _: Readonly<TreeProps<TreeDataType>>,
+    prevState: Readonly<TreeState<TreeDataType>>,
+  ): void {
+    const { activeKey } = this.state;
+
+    if (activeKey !== null && prevState.activeKey !== activeKey) {
+      this.scrollTo({ key: activeKey });
+    }
+  }
+
   componentWillUnmount() {
     window.removeEventListener('dragend', this.onWindowDragEnd);
     this.destroyed = true;
@@ -1153,9 +1164,6 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     }
 
     this.setState({ activeKey: newActiveKey });
-    if (newActiveKey !== null) {
-      this.scrollTo({ key: newActiveKey });
-    }
 
     if (onActiveChange) {
       onActiveChange(newActiveKey);

From ff6436f631fd6e98932e84578502901ba4b3ca29 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 18 Jan 2022 16:17:32 +0800
Subject: [PATCH 244/397] 5.4.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 766d9b45..8f31842f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.4.1",
+  "version": "5.4.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 70cdf723295eaa8d7dc0cf24736611cb84fc0ba5 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 18 Jan 2022 17:37:50 +0800
Subject: [PATCH 245/397] fix: scrollTo key not work with fieldNames

---
 src/NodeList.tsx |  5 +----
 src/Tree.tsx     | 31 +++++++++++++++++++------------
 2 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 8b5bbbf5..1290d30f 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -110,10 +110,7 @@ export function getMinimumRangeTransitionRange(
 }
 
 function itemKey(item: FlattenNode) {
-  const {
-    data: { key },
-    pos,
-  } = item;
+  const { key, pos } = item;
   return getKey(key, pos);
 }
 
diff --git a/src/Tree.tsx b/src/Tree.tsx
index b998f913..fdb458b5 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -307,14 +307,23 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
   listRef = React.createRef<NodeListRef>();
 
-  componentDidUpdate(
-    _: Readonly<TreeProps<TreeDataType>>,
-    prevState: Readonly<TreeState<TreeDataType>>,
-  ): void {
-    const { activeKey } = this.state;
+  componentDidMount(): void {
+    this.onUpdated();
+  }
+
+  componentDidUpdate(): void {
+    this.onUpdated();
+  }
+
+  onUpdated() {
+    const { activeKey } = this.props;
 
-    if (activeKey !== null && prevState.activeKey !== activeKey) {
-      this.scrollTo({ key: activeKey });
+    if (activeKey !== this.state.activeKey) {
+      this.setState({ activeKey });
+
+      if (activeKey !== null) {
+        this.scrollTo({ key: activeKey });
+      }
     }
   }
 
@@ -442,11 +451,6 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       newState.loadedKeys = props.loadedKeys;
     }
 
-    // ================== activeKey ==================
-    if (needSync('activeKey')) {
-      newState.activeKey = props.activeKey;
-    }
-
     return newState;
   }
 
@@ -1164,6 +1168,9 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     }
 
     this.setState({ activeKey: newActiveKey });
+    if (newActiveKey !== null) {
+      this.scrollTo({ key: newActiveKey });
+    }
 
     if (onActiveChange) {
       onActiveChange(newActiveKey);

From 6067c64ce56cbc0ba0aa764de8257782d3e78c10 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 18 Jan 2022 17:52:21 +0800
Subject: [PATCH 246/397] test: Fix test case

---
 src/Tree.tsx                | 2 +-
 tests/Accessibility.spec.js | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index fdb458b5..154315e4 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -318,7 +318,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
   onUpdated() {
     const { activeKey } = this.props;
 
-    if (activeKey !== this.state.activeKey) {
+    if (activeKey !== undefined && activeKey !== this.state.activeKey) {
       this.setState({ activeKey });
 
       if (activeKey !== null) {
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.js
index 4ade59b2..b4940165 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.js
@@ -253,6 +253,7 @@ describe('Tree Accessibility', () => {
       expect(wrapper.exists('.rc-tree-treenode-active')).toBeFalsy();
 
       wrapper.setProps({ activeKey: 'parent' });
+      wrapper.update();
       expect(wrapper.exists('.rc-tree-treenode-active')).toBeTruthy();
     });
 
@@ -272,6 +273,7 @@ describe('Tree Accessibility', () => {
       expect(wrapper.exists('.rc-tree-treenode-active')).toBeFalsy();
 
       wrapper.setProps({ activeKey: 'parent' });
+      wrapper.update();
       expect(wrapper.exists('.rc-tree-treenode-active')).toBeTruthy();
     });
   });

From 61aed0a0e56ac25e68ccb162ea74f6530d4db8a9 Mon Sep 17 00:00:00 2001
From: GEARLESS JOE <31471551+berber1016@users.noreply.github.com>
Date: Wed, 19 Jan 2022 10:52:09 +0800
Subject: [PATCH 247/397] refactor(optional chaining): use Optional Chaining
 replace if(funtion ){function()} (#527)

use Typescript version >= 3.7,we can use Optional Chaining replace
---
 src/Tree.tsx | 124 +++++++++++++++++++--------------------------------
 src/util.tsx |   2 +-
 2 files changed, 47 insertions(+), 79 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 154315e4..cdc7a8b8 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -477,9 +477,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
     window.addEventListener('dragend', this.onWindowDragEnd);
 
-    if (onDragStart) {
-      onDragStart({ event, node: convertNodePropsToEventData(node.props) });
-    }
+    onDragStart?.({ event, node: convertNodePropsToEventData(node.props) });
   };
 
   /**
@@ -564,13 +562,11 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
           this.setExpandedKeys(newExpandedKeys);
         }
 
-        if (onExpand) {
-          onExpand(newExpandedKeys, {
-            node: convertNodePropsToEventData(node.props),
-            expanded: true,
-            nativeEvent: event.nativeEvent,
-          });
-        }
+        onExpand?.(newExpandedKeys, {
+          node: convertNodePropsToEventData(node.props),
+          expanded: true,
+          nativeEvent: event.nativeEvent,
+        });
       }, 800);
     }
 
@@ -591,13 +587,11 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       dropAllowed,
     });
 
-    if (onDragEnter) {
-      onDragEnter({
-        event,
-        node: convertNodePropsToEventData(node.props),
-        expandedKeys,
-      });
-    }
+    onDragEnter?.({
+      event,
+      node: convertNodePropsToEventData(node.props),
+      expandedKeys,
+    });
   };
 
   onNodeDragOver = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance<TreeDataType>) => {
@@ -674,9 +668,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       });
     }
 
-    if (onDragOver) {
-      onDragOver({ event, node: convertNodePropsToEventData(node.props) });
-    }
+    onDragOver?.({ event, node: convertNodePropsToEventData(node.props) });
   };
 
   onNodeDragLeave: NodeDragEventHandler = (event, node) => {
@@ -692,9 +684,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
     const { onDragLeave } = this.props;
 
-    if (onDragLeave) {
-      onDragLeave({ event, node: convertNodePropsToEventData(node.props) });
-    }
+    onDragLeave?.({ event, node: convertNodePropsToEventData(node.props) });
   };
 
   // since stopPropagation() is called in treeNode
@@ -705,7 +695,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
   };
 
   // if onNodeDragEnd is called, onWindowDragEnd won't be called since stopPropagation() is called
-  onNodeDragEnd: NodeDragEventHandler = (event, node, outsideTree = false) => {
+  onNodeDragEnd: NodeDragEventHandler = (event, node) => {
     const { onDragEnd } = this.props;
     this.setState({
       dragOverNodeKey: null,
@@ -713,9 +703,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
     this.cleanDragState();
 
-    if (onDragEnd && !outsideTree) {
-      onDragEnd({ event, node: convertNodePropsToEventData(node.props) });
-    }
+    onDragEnd?.({ event, node: convertNodePropsToEventData(node.props) });
 
     this.dragNode = null;
   };
@@ -758,8 +746,8 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       dropPosition: dropPosition + Number(posArr[posArr.length - 1]),
     };
 
-    if (onDrop && !outsideTree) {
-      onDrop(dropResult);
+    if (!outsideTree) {
+      onDrop?.(dropResult);
     }
 
     this.dragNode = null;
@@ -796,16 +784,13 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
   onNodeClick: NodeMouseEventHandler = (e, treeNode) => {
     const { onClick } = this.props;
-    if (onClick) {
-      onClick(e, treeNode);
-    }
+    onClick?.(e, treeNode);
   };
 
   onNodeDoubleClick: NodeMouseEventHandler = (e, treeNode) => {
     const { onDoubleClick } = this.props;
-    if (onDoubleClick) {
-      onDoubleClick(e, treeNode);
-    }
+
+    onDoubleClick?.(e, treeNode);
   };
 
   onNodeSelect: NodeMouseEventHandler = (e, treeNode) => {
@@ -837,15 +822,13 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
     this.setUncontrolledState({ selectedKeys });
 
-    if (onSelect) {
-      onSelect(selectedKeys, {
-        event: 'select',
-        selected: targetSelected,
-        node: treeNode,
-        selectedNodes,
-        nativeEvent: e.nativeEvent,
-      });
-    }
+    onSelect?.(selectedKeys, {
+      event: 'select',
+      selected: targetSelected,
+      node: treeNode,
+      selectedNodes,
+      nativeEvent: e.nativeEvent,
+    });
   };
 
   onNodeCheck = (
@@ -928,9 +911,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       );
     }
 
-    if (onCheck) {
-      onCheck(checkedObj, eventObj as CheckInfo<TreeDataType>);
-    }
+    onCheck?.(checkedObj, eventObj as CheckInfo<TreeDataType>);
   };
 
   onNodeLoad = (treeNode: EventDataNode) => {
@@ -954,12 +935,10 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
             // onLoad should trigger before internal setState to avoid `loadData` trigger twice.
             // https://github.com/ant-design/ant-design/issues/12464
-            if (onLoad) {
-              onLoad(newLoadedKeys, {
-                event: 'load',
-                node: treeNode,
-              });
-            }
+            onLoad?.(newLoadedKeys, {
+              event: 'load',
+              node: treeNode,
+            });
 
             this.setUncontrolledState({
               loadedKeys: newLoadedKeys,
@@ -1005,16 +984,15 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
   onNodeMouseEnter: NodeMouseEventHandler = (event, node) => {
     const { onMouseEnter } = this.props;
-    if (onMouseEnter) {
-      onMouseEnter({ event, node });
-    }
+
+    onMouseEnter?.({ event, node });
+    
   };
 
   onNodeMouseLeave: NodeMouseEventHandler = (event, node) => {
     const { onMouseLeave } = this.props;
-    if (onMouseLeave) {
-      onMouseLeave({ event, node });
-    }
+    
+    onMouseLeave?.({ event, node });
   };
 
   onNodeContextMenu: NodeMouseEventHandler = (event, node) => {
@@ -1029,9 +1007,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     const { onFocus } = this.props;
     this.setState({ focused: true });
 
-    if (onFocus) {
-      onFocus(...args);
-    }
+    onFocus?.(...args);
   };
 
   onBlur: React.FocusEventHandler<HTMLDivElement> = (...args) => {
@@ -1039,9 +1015,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     this.setState({ focused: false });
     this.onActiveChange(null);
 
-    if (onBlur) {
-      onBlur(...args);
-    }
+    onBlur?.(...args);
   };
 
   getTreeNodeRequiredProps = () => {
@@ -1113,13 +1087,11 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
 
     this.setExpandedKeys(expandedKeys);
 
-    if (onExpand) {
-      onExpand(expandedKeys, {
-        node: treeNode,
-        expanded: targetExpanded,
-        nativeEvent: e.nativeEvent,
-      });
-    }
+    onExpand?.(expandedKeys, {
+      node: treeNode,
+      expanded: targetExpanded,
+      nativeEvent: e.nativeEvent,
+    });
 
     // Async Load data
     if (targetExpanded && loadData) {
@@ -1172,9 +1144,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       this.scrollTo({ key: newActiveKey });
     }
 
-    if (onActiveChange) {
-      onActiveChange(newActiveKey);
-    }
+    onActiveChange?.(newActiveKey);
   };
 
   getActiveItem = () => {
@@ -1288,9 +1258,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       }
     }
 
-    if (onKeyDown) {
-      onKeyDown(event);
-    }
+    onKeyDown?.(event);
   };
 
   /**
diff --git a/src/util.tsx b/src/util.tsx
index 5e9d4e33..147a57a7 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -290,7 +290,7 @@ export function convertDataToTree(
   return list.map(({ children, ...props }): NodeElement => {
     const childrenNodes = convertDataToTree(children, processor);
 
-    return <TreeNode {...processProps(props)}>{childrenNodes}</TreeNode>;
+    return <TreeNode key={props.key} {...processProps(props)}>{childrenNodes}</TreeNode>;
   });
 }
 

From 22c5ace87f7c27a452b444a63e1a927488525520 Mon Sep 17 00:00:00 2001
From: Shaleen Kachhara <shaleenjain449@gmail.com>
Date: Mon, 24 Jan 2022 16:48:15 +0530
Subject: [PATCH 248/397] chore(dep-update): rc-virtual-list version upgrade
 (#528)

Co-authored-by: Shaleen Kachhara <Shaleen.Kachhara1@aexp.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8f31842f..c71f7bb4 100644
--- a/package.json
+++ b/package.json
@@ -77,6 +77,6 @@
     "classnames": "2.x",
     "rc-motion": "^2.0.1",
     "rc-util": "^5.16.1",
-    "rc-virtual-list": "^3.4.1"
+    "rc-virtual-list": "^3.4.2"
   }
 }

From 0ccd6ffcac070a0c04e8ad95f57b25b1d1fa2177 Mon Sep 17 00:00:00 2001
From: Glean Wu <772098484@qq.com>
Date: Thu, 10 Feb 2022 11:53:41 +0800
Subject: [PATCH 249/397] Update README.md (#532)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

添加样式引入备注
---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 1f78a5f3..11078a70 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ online example: https://tree.react-component.now.sh/
 [![rc-tree](https://nodei.co/npm/rc-tree.png)](https://npmjs.org/package/rc-tree)
 
 ## Usage
+> Note: `import "rc-tree/assets/index.css"`
 
 see examples
 

From ec78e4df54e97a1bfad0807af654ebd73f6663ca Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Mon, 28 Feb 2022 17:59:40 +0800
Subject: [PATCH 250/397] fix: motion should work with fieldNames (#540)

* fix: motion should work with fieldNames

* chore: code clean
---
 src/NodeList.tsx         |  4 ++--
 src/utils/diffUtil.ts    |  6 +++---
 tests/TreeMotion.spec.js | 34 ++++++++++++++++++++++++++++++++++
 3 files changed, 39 insertions(+), 5 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 1290d30f..aeb6662a 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -200,7 +200,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
 
     if (diffExpanded.key !== null) {
       if (diffExpanded.add) {
-        const keyIndex = prevData.findIndex(({ data: { key } }) => key === diffExpanded.key);
+        const keyIndex = prevData.findIndex(({ key }) => key === diffExpanded.key);
 
         const rangeNodes = getMinimumRangeTransitionRange(
           getExpandRange(prevData, data, diffExpanded.key),
@@ -216,7 +216,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
         setTransitionRange(rangeNodes);
         setMotionType('show');
       } else {
-        const keyIndex = data.findIndex(({ data: { key } }) => key === diffExpanded.key);
+        const keyIndex = data.findIndex(({ key }) => key === diffExpanded.key);
 
         const rangeNodes = getMinimumRangeTransitionRange(
           getExpandRange(data, prevData, diffExpanded.key),
diff --git a/src/utils/diffUtil.ts b/src/utils/diffUtil.ts
index 6dd83a6a..85496435 100644
--- a/src/utils/diffUtil.ts
+++ b/src/utils/diffUtil.ts
@@ -33,12 +33,12 @@ export function findExpandedKeys(prev: Key[] = [], next: Key[] = []) {
 }
 
 export function getExpandRange(shorter: FlattenNode[], longer: FlattenNode[], key: Key) {
-  const shorterStartIndex = shorter.findIndex(({ data }) => data.key === key);
+  const shorterStartIndex = shorter.findIndex(data => data.key === key);
   const shorterEndNode = shorter[shorterStartIndex + 1];
-  const longerStartIndex = longer.findIndex(({ data }) => data.key === key);
+  const longerStartIndex = longer.findIndex(data => data.key === key);
 
   if (shorterEndNode) {
-    const longerEndIndex = longer.findIndex(({ data }) => data.key === shorterEndNode.data.key);
+    const longerEndIndex = longer.findIndex(data => data.key === shorterEndNode.key);
     return longer.slice(longerStartIndex + 1, longerEndIndex);
   }
   return longer.slice(longerStartIndex + 1);
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.js
index 78c86fb8..e12527c0 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.js
@@ -148,4 +148,38 @@ describe('Tree Motion', () => {
       expect(onMotionEnd).not.toHaveBeenCalled();
     });
   });
+
+  it('motion should work well with fieldNames', () => {
+    const Demo = () => (
+      <Tree
+        defaultExpandAll
+        fieldNames={{
+          title: 'name',
+          key: 'id',
+          children: 'sub',
+        }}
+        motion={{
+          motionName: 'bamboo',
+        }}
+        treeData={[
+          {
+            id: '1',
+            name: 'A',
+            sub: [
+              {
+                id: '2',
+                name: 'B',
+                sub: [],
+              },
+            ],
+          },
+        ]}
+      />
+    );
+
+    const wrapper = mount(<Demo />);
+    expect(wrapper.find('[title="B"]').length).toBeTruthy();
+    wrapper.find('.rc-tree-switcher').first().simulate('click');
+    expect(wrapper.find('[title="B"]').length).toBe(0);
+  });
 });

From f565965430c896b11fc51831aeab4c3b4e9572fb Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Mon, 28 Feb 2022 18:05:55 +0800
Subject: [PATCH 251/397] v5.4.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c71f7bb4..4020233a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.4.2",
+  "version": "5.4.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 111e71743d53770b677be17f674d7035b7b1be07 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Fri, 18 Mar 2022 10:59:09 +0800
Subject: [PATCH 252/397] feat: add rootStyle and rootClassName (#547)

* feat: add rootStyle and rootClassName

* test: add test case
---
 src/Tree.tsx                          |  11 +-
 tests/Tree.spec.js                    |  17 ++
 tests/__snapshots__/Tree.spec.js.snap | 280 ++++++++++++++++++++++++++
 3 files changed, 305 insertions(+), 3 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index cdc7a8b8..685dd3ea 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -185,6 +185,9 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
 
   // direction for drag logic
   direction?: Direction;
+
+  rootClassName?: string;
+  rootStyle?: React.CSSProperties;
 }
 
 interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
@@ -986,12 +989,11 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     const { onMouseEnter } = this.props;
 
     onMouseEnter?.({ event, node });
-    
   };
 
   onNodeMouseLeave: NodeMouseEventHandler = (event, node) => {
     const { onMouseLeave } = this.props;
-    
+
     onMouseLeave?.({ event, node });
   };
 
@@ -1337,6 +1339,8 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       onContextMenu,
       onScroll,
       direction,
+      rootClassName,
+      rootStyle,
     } = this.props;
     const domProps: React.HTMLAttributes<HTMLDivElement> = pickAttrs(this.props, {
       aria: true,
@@ -1404,11 +1408,12 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
       >
         <div
           role="tree"
-          className={classNames(prefixCls, className, {
+          className={classNames(prefixCls, className, rootClassName, {
             [`${prefixCls}-show-line`]: showLine,
             [`${prefixCls}-focused`]: focused,
             [`${prefixCls}-active-focused`]: activeKey !== null,
           })}
+          style={rootStyle}
         >
           <NodeList
             ref={this.listRef}
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.js
index e5e81143..13eeba36 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.js
@@ -1104,4 +1104,21 @@ describe('Tree Basic', () => {
     wrapper.find('.rc-tree-list-holder').simulate('scroll');
     expect(onScroll).toBeCalled();
   });
+
+  it('should support rootStyle and rootClassName', () => {
+    const data = [];
+    for (let i = 0; i < 9; i += 1) {
+      data.push({
+        key: i,
+        title: i,
+      });
+    }
+    const tree = mount(
+      <Tree treeData={data} rootClassName="root-tree" rootStyle={{ backgroundColor: 'cyan' }} />,
+    );
+
+    expect(tree.render()).toMatchSnapshot();
+    expect(tree.getDOMNode().className).toContain('root-tree');
+    expect(tree.getDOMNode().style.backgroundColor).toBe('cyan');
+  });
 });
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.js.snap
index 54c863ed..72ef9943 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.js.snap
@@ -857,3 +857,283 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
   </div>
 </div>
 `;
+
+exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
+<div
+  class="rc-tree root-tree"
+  role="tree"
+  style="background-color: cyan;"
+>
+  <div>
+    <input
+      aria-label="for screen reader"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
+      tabindex="0"
+      value=""
+    />
+  </div>
+  <div
+    aria-hidden="true"
+    class="rc-tree-treenode"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+  >
+    <div
+      class="rc-tree-indent"
+    >
+      <div
+        class="rc-tree-indent-unit"
+      />
+    </div>
+  </div>
+  <div
+    class="rc-tree-list"
+    style="position: relative;"
+  >
+    <div
+      class="rc-tree-list-holder"
+    >
+      <div>
+        <div
+          class="rc-tree-list-holder-inner"
+          style="display: flex; flex-direction: column;"
+        >
+          <div
+            aria-grabbed="false"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
+          >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title=""
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                0
+              </span>
+            </span>
+          </div>
+          <div
+            aria-grabbed="false"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
+          >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title=""
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                1
+              </span>
+            </span>
+          </div>
+          <div
+            aria-grabbed="false"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
+          >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title=""
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                2
+              </span>
+            </span>
+          </div>
+          <div
+            aria-grabbed="false"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
+          >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title=""
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                3
+              </span>
+            </span>
+          </div>
+          <div
+            aria-grabbed="false"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
+          >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title=""
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                4
+              </span>
+            </span>
+          </div>
+          <div
+            aria-grabbed="false"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
+          >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title=""
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                5
+              </span>
+            </span>
+          </div>
+          <div
+            aria-grabbed="false"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
+          >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title=""
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                6
+              </span>
+            </span>
+          </div>
+          <div
+            aria-grabbed="false"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            draggable="false"
+          >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title=""
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                7
+              </span>
+            </span>
+          </div>
+          <div
+            aria-grabbed="false"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            draggable="false"
+          >
+            <span
+              aria-hidden="true"
+              class="rc-tree-indent"
+            />
+            <span
+              class="rc-tree-switcher rc-tree-switcher-noop"
+            />
+            <span
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
+              title=""
+            >
+              <span
+                class="rc-tree-iconEle rc-tree-icon__docu"
+              />
+              <span
+                class="rc-tree-title"
+              >
+                8
+              </span>
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+`;

From f5817242185cc7c3cbd42cc161a0cee8ffb54458 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Fri, 18 Mar 2022 11:29:36 +0800
Subject: [PATCH 253/397] v5.5.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4020233a..c4f4eb19 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.4.4",
+  "version": "5.5.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 136b7b9af7da0fa3e56a87f846755c54d4aa9bbd Mon Sep 17 00:00:00 2001
From: Heaven <18418010+NE-SmallTown@users.noreply.github.com>
Date: Tue, 26 Apr 2022 15:13:59 +0800
Subject: [PATCH 254/397] Feat: add 'expandAction' prop for Tree (#566)

---
 README.md                       |   1 +
 docs/demo/expandAction.md       |   3 +
 docs/examples/expandAction.jsx  |  23 ++++++
 docs/examples/expandAction.less |   4 +
 src/Tree.tsx                    |  31 ++++++-
 tests/TreeProps.spec.js         | 142 ++++++++++++++++++++++++++++++++
 6 files changed, 202 insertions(+), 2 deletions(-)
 create mode 100644 docs/demo/expandAction.md
 create mode 100644 docs/examples/expandAction.jsx
 create mode 100644 docs/examples/expandAction.less

diff --git a/README.md b/README.md
index 11078a70..57ad6046 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,7 @@ see examples
 | allowDrop | Whether to allow drop on node | ({ dragNode, dropNode, dropPosition }) => boolean | - |
 | dropIndicatorRender | The indicator to render when dragging | ({ dropPosition, dropLevelOffset, indent: number, prefixCls }) => ReactNode| - |
 | direction | Display direction of the tree, it may affect dragging behavior | `ltr` \| `rtl` | - |
+| expandAction | Tree open logic, optional: false \| `click` \| `doubleClick` | string \| boolean | `click` |
 
 ### TreeNode props
 
diff --git a/docs/demo/expandAction.md b/docs/demo/expandAction.md
new file mode 100644
index 00000000..8be97a03
--- /dev/null
+++ b/docs/demo/expandAction.md
@@ -0,0 +1,3 @@
+## expandAction
+
+<code src="../examples/expandAction.jsx">
diff --git a/docs/examples/expandAction.jsx b/docs/examples/expandAction.jsx
new file mode 100644
index 00000000..e6482d2c
--- /dev/null
+++ b/docs/examples/expandAction.jsx
@@ -0,0 +1,23 @@
+/* eslint-disable no-console, react/no-access-state-in-setstate */
+import React from 'react';
+import '../../assets/index.less';
+import Tree, { TreeNode } from 'rc-tree';
+
+const Demo = () => (
+  <div className="expandAction-demo">
+    <h2>expandAction</h2>
+    <p>normal</p>
+    <Tree defaultExpandedKeys={['0-0']} expandAction="click" selectable={false}>
+      <TreeNode title="parent 1" key="0-0">
+        <TreeNode
+          title="click title can trigger expand even selectable is false because expandAction is 'click'"
+          key="0-0-0"
+        >
+          <TreeNode title="leaf-1" key="0-0-0-0" />
+        </TreeNode>
+      </TreeNode>
+    </Tree>
+  </div>
+);
+
+export default Demo;
diff --git a/docs/examples/expandAction.less b/docs/examples/expandAction.less
new file mode 100644
index 00000000..b201eb71
--- /dev/null
+++ b/docs/examples/expandAction.less
@@ -0,0 +1,4 @@
+@import '../../assets/index.less';
+.expandAction-demo {
+  padding: 0 20px;
+}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 685dd3ea..be70bf03 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -78,6 +78,8 @@ export type DraggableConfig = {
   nodeDraggable?: DraggableFn;
 };
 
+export type ExpandAction = false | 'click' | 'doubleClick';
+
 export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   prefixCls: string;
   className?: string;
@@ -92,6 +94,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   showIcon?: boolean;
   icon?: IconType;
   selectable?: boolean;
+  expandAction: ExpandAction;
   disabled?: boolean;
   multiple?: boolean;
   checkable?: boolean | React.ReactNode;
@@ -250,6 +253,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     defaultSelectedKeys: [],
     dropIndicatorRender: DropIndicator,
     allowDrop: () => true,
+    expandAction: false,
   };
 
   static TreeNode = TreeNode;
@@ -785,13 +789,36 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
     this.currentMouseOverDroppableNodeKey = null;
   };
 
+  triggerExpandActionExpand: NodeMouseEventHandler = (e, treeNode) => {
+    const { expandedKeys, flattenNodes } = this.state;
+    const { expanded, key } = treeNode;
+
+    const node = flattenNodes.filter(nodeItem => nodeItem.key === key)[0];
+    const eventNode = convertNodePropsToEventData({
+      ...getTreeNodeProps(key, this.getTreeNodeRequiredProps()),
+      data: node.data,
+    });
+
+    this.setExpandedKeys(expanded ? arrDel(expandedKeys, key) : arrAdd(expandedKeys, key));
+    this.onNodeExpand(e as React.MouseEvent<HTMLDivElement>, eventNode);
+  };
+
   onNodeClick: NodeMouseEventHandler = (e, treeNode) => {
-    const { onClick } = this.props;
+    const { onClick, expandAction } = this.props;
+
+    if (expandAction === 'click') {
+      this.triggerExpandActionExpand(e, treeNode);
+    }
+
     onClick?.(e, treeNode);
   };
 
   onNodeDoubleClick: NodeMouseEventHandler = (e, treeNode) => {
-    const { onDoubleClick } = this.props;
+    const { onDoubleClick, expandAction } = this.props;
+
+    if (expandAction === 'doubleClick') {
+      this.triggerExpandActionExpand(e, treeNode);
+    }
 
     onDoubleClick?.(e, treeNode);
   };
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.js
index 4f7834dd..7d9a4670 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.js
@@ -186,6 +186,148 @@ describe('Tree Props', () => {
     });
   });
 
+  describe('expandAction with selectable props', () => {
+    it('title expandable when selectable is false and expandAction is "click"', () => {
+      const onClick = jest.fn();
+      const onSelect = jest.fn();
+      const onExpand = jest.fn();
+
+      const wrapper = mount(
+        <Tree
+          onClick={onClick}
+          onSelect={onSelect}
+          onExpand={onExpand}
+          defaultExpandedKeys={['0-0']}
+          selectable={false}
+          expandAction="click"
+        >
+          <TreeNode title="parent 1" key="0-0">
+            <TreeNode title="leaf 1" key="0-0-0">
+              <TreeNode title="leaf-1" key="0-0-0-0" />
+            </TreeNode>
+
+            <TreeNode title="leaf 2" key="0-1-0">
+              <TreeNode title="leaf-2" key="0-1-0-0" />
+            </TreeNode>
+          </TreeNode>
+        </Tree>,
+      );
+
+      // test trigger expand when click title
+      wrapper.find('[title="leaf 1"]').hostNodes().simulate('click');
+
+      expect(onClick).toHaveBeenCalled();
+      expect(onSelect).not.toHaveBeenCalled();
+      expect(onExpand).toHaveBeenCalledWith(['0-0', '0-0-0'], {
+        expanded: true,
+        node: expect.anything(),
+        nativeEvent: expect.anything(),
+      });
+
+      onClick.mockReset();
+      onSelect.mockReset();
+      onExpand.mockReset();
+
+      // test trigger un-expand when click title again
+      wrapper.find('[title="leaf 1"]').hostNodes().simulate('click');
+
+      expect(onClick).toHaveBeenCalled();
+      expect(onSelect).not.toHaveBeenCalled();
+      expect(onExpand).toHaveBeenCalledWith(['0-0'], {
+        expanded: false,
+        node: expect.anything(),
+        nativeEvent: expect.anything(),
+      });
+    });
+
+    it('title expandable when selectable is false and expandAction is "doubleClick"', () => {
+      const onDoubleClick = jest.fn();
+      const onSelect = jest.fn();
+      const onExpand = jest.fn();
+
+      const wrapper = mount(
+        <Tree
+          onDoubleClick={onDoubleClick}
+          onSelect={onSelect}
+          onExpand={onExpand}
+          defaultExpandedKeys={['0-0']}
+          selectable={false}
+          expandAction="doubleClick"
+        >
+          <TreeNode title="parent 1" key="0-0">
+            <TreeNode title="leaf 1" key="0-0-0">
+              <TreeNode title="leaf-1" key="0-0-0-0" />
+            </TreeNode>
+
+            <TreeNode title="leaf 2" key="0-1-0">
+              <TreeNode title="leaf-2" key="0-1-0-0" />
+            </TreeNode>
+          </TreeNode>
+        </Tree>,
+      );
+
+      // test trigger expand when double click title
+      wrapper.find('[title="leaf 1"]').hostNodes().simulate('doubleclick');
+
+      expect(onDoubleClick).toHaveBeenCalled();
+      expect(onSelect).not.toHaveBeenCalled();
+      expect(onExpand).toHaveBeenCalledWith(['0-0', '0-0-0'], {
+        expanded: true,
+        node: expect.anything(),
+        nativeEvent: expect.anything(),
+      });
+
+      onDoubleClick.mockReset();
+      onSelect.mockReset();
+      onExpand.mockReset();
+
+      // test trigger un-expand when double click title again
+      wrapper.find('[title="leaf 1"]').hostNodes().simulate('doubleclick');
+
+      expect(onDoubleClick).toHaveBeenCalled();
+      expect(onSelect).not.toHaveBeenCalled();
+      expect(onExpand).toHaveBeenCalledWith(['0-0'], {
+        expanded: false,
+        node: expect.anything(),
+        nativeEvent: expect.anything(),
+      });
+    });
+
+    it('title un-expandable when selectable is false and expandAction is false', () => {
+      const onClick = jest.fn();
+      const onSelect = jest.fn();
+      const onExpand = jest.fn();
+
+      const wrapper = mount(
+        <Tree
+          onClick={onClick}
+          onSelect={onSelect}
+          onExpand={onExpand}
+          defaultExpandedKeys={['0-0']}
+          selectable={false}
+          expandAction={false}
+        >
+          <TreeNode title="parent 1" key="0-0">
+            <TreeNode title="leaf 1" key="0-0-0">
+              <TreeNode title="leaf-1" key="0-0-0-0" />
+            </TreeNode>
+
+            <TreeNode title="leaf 2" key="0-1-0">
+              <TreeNode title="leaf-2" key="0-1-0-0" />
+            </TreeNode>
+          </TreeNode>
+        </Tree>,
+      );
+
+      // test won't trigger expand when click title if expandAction is false
+      wrapper.find('[title="leaf 2"]').hostNodes().simulate('click');
+
+      expect(onClick).toHaveBeenCalled();
+      expect(onSelect).not.toHaveBeenCalled();
+      expect(onExpand).not.toHaveBeenCalled();
+    });
+  });
+
   // multiple - this prop works with selectable
   it('multiple', () => {
     const handleOnSelect = jest.fn();

From c2f47a44314bed45d3347e998283439cbc3c7add Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 26 Apr 2022 15:19:30 +0800
Subject: [PATCH 255/397] 5.6.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c4f4eb19..d3a4c924 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.5.0",
+  "version": "5.6.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 57c6b77198aab0a6075a523ac66f0c4ed8100208 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 26 Apr 2022 18:23:58 +0800
Subject: [PATCH 256/397] fix: NodeList sync should not use closure ood data
 (#570)

* test: test driven

* fix: sync with latest list
---
 src/NodeList.tsx         | 10 +++++--
 tests/TreeMotion.spec.js | 59 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index aeb6662a..9f6349b8 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -183,9 +183,15 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
   const [transitionRange, setTransitionRange] = React.useState([]);
   const [motionType, setMotionType] = React.useState<'show' | 'hide' | null>(null);
 
+  // When motion end but data change, this will makes data back to previous one
+  const dataRef = React.useRef(data);
+  dataRef.current = data;
+
   function onMotionEnd() {
-    setPrevData(data);
-    setTransitionData(data);
+    const latestData = dataRef.current;
+
+    setPrevData(latestData);
+    setTransitionData(latestData);
     setTransitionRange([]);
     setMotionType(null);
 
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.js
index e12527c0..61aad107 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.js
@@ -1,5 +1,6 @@
 import React from 'react';
 import { mount } from 'enzyme';
+import { act } from 'react-dom/test-utils';
 import Tree, { TreeNode } from '../src';
 import MotionTreeNode from '../src/MotionTreeNode';
 import { TreeContext } from '../src/contextTypes';
@@ -182,4 +183,62 @@ describe('Tree Motion', () => {
     wrapper.find('.rc-tree-switcher').first().simulate('click');
     expect(wrapper.find('[title="B"]').length).toBe(0);
   });
+
+  it('motion should not revert flatten list', () => {
+    jest.useFakeTimers();
+
+    const wrapper = mount(
+      <Tree
+        motion={{
+          motionName: 'little',
+        }}
+        height={100}
+        itemHeight={10}
+        treeData={[
+          {
+            key: 'parent',
+            title: 'parent',
+            children: [
+              {
+                key: 'child',
+                title: 'child',
+              },
+            ],
+          },
+        ]}
+      />,
+    );
+
+    wrapper.setProps({
+      expandedKeys: ['parent'],
+    });
+
+    for (let i = 0; i < 10; i += 1) {
+      act(() => {
+        jest.runAllTimers();
+      });
+    }
+
+    wrapper.setProps({
+      treeData: [
+        {
+          key: 'parent',
+          title: 'parent2',
+          children: [
+            {
+              key: 'child',
+              title: 'child2',
+            },
+          ],
+        },
+      ],
+    });
+
+    wrapper.find('.rc-tree-treenode-motion').simulate('animationEnd');
+
+    expect(wrapper.find('span.rc-tree-title').at(0).text()).toEqual('parent2');
+    expect(wrapper.find('span.rc-tree-title').at(1).text()).toEqual('child2');
+
+    jest.useRealTimers();
+  });
 });

From 7d3d495ec3cf77d1a15c0aab5780df98bb0ca96a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 26 Apr 2022 18:24:32 +0800
Subject: [PATCH 257/397] 5.6.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d3a4c924..a4f05c97 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.0",
+  "version": "5.6.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From df8515570fe4e47b80c34c2ec431ab979a6511e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Fri, 13 May 2022 21:32:38 +0800
Subject: [PATCH 258/397] fix: React 18 StrictMode not work with expand & check
 (#581)

* test: test driven

* test: more test

* fix: StrictMode broken

* test: convert

* test: more 18 test

* test: more 18 test

* test: more 18 test

* test: more 18 test

* test: more 18 test

* test: more 18 test

* test: more 18 test

* test: more 18 test

* test: more 18 test

* test: more and more

* test: all test case

* chore: fix comiple issue

* test: try delay
---
 jest.config.js                                |    1 +
 package.json                                  |   10 +-
 src/MotionTreeNode.tsx                        |    2 +-
 src/NodeList.tsx                              |    9 +-
 src/Tree.tsx                                  |    5 +-
 src/index.ts                                  |    4 +-
 src/interface.tsx                             |    7 +-
 src/util.tsx                                  |   10 +-
 ...ibility.spec.js => Accessibility.spec.tsx} |  190 +--
 tests/FieldNames.spec.tsx                     |  123 +-
 tests/React18.spec.tsx                        |   68 +
 tests/{Tree.spec.js => Tree.spec.tsx}         |  678 +++++-----
 tests/TreeDraggable.spec.js                   |  798 ------------
 tests/TreeDraggable.spec.tsx                  | 1118 +++++++++++++++++
 ...TreeMotion.spec.js => TreeMotion.spec.tsx} |  177 +--
 ...deProps.spec.js => TreeNodeProps.spec.tsx} |   54 +-
 .../{TreeProps.spec.js => TreeProps.spec.tsx} |  411 +++---
 .../{Tree.spec.js.snap => Tree.spec.tsx.snap} |   24 +-
 ...ec.js.snap => TreeNodeProps.spec.tsx.snap} |   72 +-
 ...s.spec.js.snap => TreeProps.spec.tsx.snap} |  152 +--
 tests/setupFilesAfterEnv.js                   |    1 +
 tests/type.spec.tsx                           |    6 +-
 tests/{util.js => util.ts}                    |    2 +-
 23 files changed, 2224 insertions(+), 1698 deletions(-)
 rename tests/{Accessibility.spec.js => Accessibility.spec.tsx} (52%)
 create mode 100644 tests/React18.spec.tsx
 rename tests/{Tree.spec.js => Tree.spec.tsx} (57%)
 delete mode 100644 tests/TreeDraggable.spec.js
 create mode 100644 tests/TreeDraggable.spec.tsx
 rename tests/{TreeMotion.spec.js => TreeMotion.spec.tsx} (54%)
 rename tests/{TreeNodeProps.spec.js => TreeNodeProps.spec.tsx} (76%)
 rename tests/{TreeProps.spec.js => TreeProps.spec.tsx} (69%)
 rename tests/__snapshots__/{Tree.spec.js.snap => Tree.spec.tsx.snap} (96%)
 rename tests/__snapshots__/{TreeNodeProps.spec.js.snap => TreeNodeProps.spec.tsx.snap} (90%)
 rename tests/__snapshots__/{TreeProps.spec.js.snap => TreeProps.spec.tsx.snap} (90%)
 create mode 100644 tests/setupFilesAfterEnv.js
 rename tests/{util.js => util.ts} (96%)

diff --git a/jest.config.js b/jest.config.js
index 66a2c875..b806b586 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,3 +1,4 @@
 module.exports = {
+  setupFilesAfterEnv: ['<rootDir>/tests/setupFilesAfterEnv.js'],
   snapshotSerializers: [require.resolve('enzyme-to-json/serializer')],
 };
diff --git a/package.json b/package.json
index a4f05c97..be5770fd 100644
--- a/package.json
+++ b/package.json
@@ -50,9 +50,11 @@
     "react-dom": "*"
   },
   "devDependencies": {
+    "@testing-library/jest-dom": "^5.16.4",
+    "@testing-library/react": "^13.0.0",
     "@types/enzyme": "^3.10.9",
     "@types/jest": "^26.0.4",
-    "@types/react": "^17.0.11",
+    "@types/react": "^18.0.0",
     "@types/react-dom": "^17.0.8",
     "@types/warning": "^3.0.0",
     "@umijs/fabric": "^2.4.8",
@@ -68,8 +70,8 @@
     "rc-dialog": "^8.1.0",
     "rc-tooltip": "5.x",
     "rc-trigger": "^5.0.7",
-    "react": "^16.8.0",
-    "react-dom": "^16.8.0",
+    "react": "^18.0.0",
+    "react-dom": "^18.0.0",
     "typescript": "^4.0.2"
   },
   "dependencies": {
@@ -77,6 +79,6 @@
     "classnames": "2.x",
     "rc-motion": "^2.0.1",
     "rc-util": "^5.16.1",
-    "rc-virtual-list": "^3.4.2"
+    "rc-virtual-list": "^3.4.8"
   }
 }
diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index d7939bfa..d8cb6717 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -94,7 +94,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
 
               return (
                 <TreeNode
-                  {...restProps}
+                  {...(restProps as Omit<typeof restProps, 'children'>)}
                   {...treeNodeProps}
                   title={title}
                   active={active}
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 9f6349b8..bd6c4a5a 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -126,7 +126,7 @@ function getAccessibilityPath(item: FlattenNode): string {
   return path;
 }
 
-const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (props, ref) => {
+const NodeList = React.forwardRef<NodeListRef, NodeListProps>((props, ref) => {
   const {
     prefixCls,
     data,
@@ -324,7 +324,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
           }
         }}
       >
-        {(treeNode: FlattenNode) => {
+        {treeNode => {
           const {
             pos,
             data: { ...restProps },
@@ -341,7 +341,7 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
 
           return (
             <MotionTreeNode
-              {...restProps}
+              {...(restProps as Omit<typeof restProps, 'children'>)}
               {...treeNodeProps}
               title={title}
               active={!!activeItem && key === activeItem.key}
@@ -364,9 +364,8 @@ const RefNodeList: React.RefForwardingComponent<NodeListRef, NodeListProps> = (p
       </VirtualList>
     </>
   );
-};
+});
 
-const NodeList = React.forwardRef(RefNodeList);
 NodeList.displayName = 'NodeList';
 
 export default NodeList;
diff --git a/src/Tree.tsx b/src/Tree.tsx
index be70bf03..0f8d2f99 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -154,7 +154,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
       node: EventDataNode;
     },
   ) => void;
-  loadData?: (treeNode: EventDataNode) => Promise<void>;
+  loadData?: (treeNode: EventDataNode) => Promise<any>;
   loadedKeys?: Key[];
   onMouseEnter?: (info: NodeMouseEventParams) => void;
   onMouseLeave?: (info: NodeMouseEventParams) => void;
@@ -231,7 +231,7 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
   fieldNames: FieldNames;
 }
 
-class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Component<
+class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends React.Component<
   TreeProps<TreeDataType>,
   TreeState<TreeDataType>
 > {
@@ -315,6 +315,7 @@ class Tree<TreeDataType extends BasicDataNode = DataNode> extends React.Componen
   listRef = React.createRef<NodeListRef>();
 
   componentDidMount(): void {
+    this.destroyed = false;
     this.onUpdated();
   }
 
diff --git a/src/index.ts b/src/index.ts
index 33720af5..e3ad61ab 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,8 +2,8 @@ import Tree from './Tree';
 import TreeNode from './TreeNode';
 import type { TreeProps } from './Tree';
 import type { TreeNodeProps } from './TreeNode';
-import type { BasicDataNode } from './interface';
+import type { BasicDataNode, FieldDataNode } from './interface';
 
 export { TreeNode };
-export type { TreeProps, TreeNodeProps, BasicDataNode };
+export type { TreeProps, TreeNodeProps, BasicDataNode, FieldDataNode };
 export default Tree;
diff --git a/src/interface.tsx b/src/interface.tsx
index c1ef7b7f..da0ef4ab 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -21,9 +21,14 @@ export interface BasicDataNode {
 export interface DataNode extends BasicDataNode {
   children?: DataNode[];
   key: string | number;
-  title?: React.ReactNode;
+  title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
 }
 
+/** Provide a wrap type define for developer to wrap with customize fieldNames data type */
+export type FieldDataNode<T, ChildFieldName extends string = 'children'> = BasicDataNode &
+  T &
+  Partial<Record<ChildFieldName, FieldDataNode<T, ChildFieldName>[]>>;
+
 export interface EventDataNode extends DataNode {
   expanded: boolean;
   selected: boolean;
diff --git a/src/util.tsx b/src/util.tsx
index 147a57a7..c5148f6c 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -5,7 +5,7 @@
 
 import React from 'react';
 import warning from 'rc-util/lib/warning';
-import TreeNode, { TreeNodeProps } from './TreeNode';
+import TreeNode from './TreeNode';
 import {
   NodeElement,
   Key,
@@ -278,7 +278,7 @@ export function calcSelectedKeys(selectedKeys: Key[], props: TreeProps) {
   return selectedKeys;
 }
 
-const internalProcessProps = (props: DataNode): Partial<TreeNodeProps> => props;
+const internalProcessProps = (props: DataNode): any => props;
 export function convertDataToTree(
   treeData: DataNode[],
   processor?: { processProps: (prop: DataNode) => any },
@@ -290,7 +290,11 @@ export function convertDataToTree(
   return list.map(({ children, ...props }): NodeElement => {
     const childrenNodes = convertDataToTree(children, processor);
 
-    return <TreeNode key={props.key} {...processProps(props)}>{childrenNodes}</TreeNode>;
+    return (
+      <TreeNode key={props.key} {...processProps(props)}>
+        {childrenNodes}
+      </TreeNode>
+    );
   });
 }
 
diff --git a/tests/Accessibility.spec.js b/tests/Accessibility.spec.tsx
similarity index 52%
rename from tests/Accessibility.spec.js
rename to tests/Accessibility.spec.tsx
index b4940165..44c86fee 100644
--- a/tests/Accessibility.spec.js
+++ b/tests/Accessibility.spec.tsx
@@ -1,9 +1,8 @@
 /* eslint-disable no-undef, react/no-multi-comp */
 import React from 'react';
 import KeyCode from 'rc-util/lib/KeyCode';
-import { mount } from 'enzyme';
-import Tree from '../src';
-import { InternalTreeNode } from '../src/TreeNode';
+import { render, fireEvent } from '@testing-library/react';
+import Tree, { FieldDataNode } from '../src';
 import { spyConsole } from './util';
 
 describe('Tree Accessibility', () => {
@@ -27,7 +26,7 @@ describe('Tree Accessibility', () => {
         onActiveChange.mockReset();
       }
 
-      const wrapper = mount(
+      const { container } = render(
         <Tree
           {...props}
           onExpand={onExpand}
@@ -40,116 +39,88 @@ describe('Tree Accessibility', () => {
         />,
       );
 
+      function keyDown(keyCode: number) {
+        fireEvent.keyDown(container.querySelector('input'), {
+          keyCode,
+        });
+      }
+
+      function getTreeNode(index: number) {
+        const treeNodes = container
+          .querySelector('.rc-tree-list-holder')
+          .querySelectorAll('.rc-tree-treenode');
+
+        return treeNodes[(index + treeNodes.length) % treeNodes.length];
+      }
+
       // Focus
-      wrapper.find('input').simulate('focus');
+      fireEvent.focus(container.querySelector('input'));
       expect(onFocus).toHaveBeenCalled();
 
       // Arrow up: last one
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.UP });
-      expect(
-        wrapper
-          .find(InternalTreeNode)
-          .find('.rc-tree-treenode')
-          .last()
-          .hasClass('rc-tree-treenode-active'),
-      ).toBeTruthy();
+      keyDown(KeyCode.UP);
+      expect(getTreeNode(-1)).toHaveClass('rc-tree-treenode-active');
       checkKeyDownTrigger();
       checkActiveTrigger('child 2');
 
       // Arrow down: first one
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
-      expect(
-        wrapper
-          .find(InternalTreeNode)
-          .find('.rc-tree-treenode')
-          .first()
-          .hasClass('rc-tree-treenode-active'),
-      ).toBeTruthy();
+      keyDown(KeyCode.DOWN);
+      expect(getTreeNode(0)).toHaveClass('rc-tree-treenode-active');
       checkKeyDownTrigger();
       checkActiveTrigger('parent');
 
       // Arrow up: last one again
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.UP });
-      expect(
-        wrapper
-          .find(InternalTreeNode)
-          .find('.rc-tree-treenode')
-          .last()
-          .hasClass('rc-tree-treenode-active'),
-      ).toBeTruthy();
+      keyDown(KeyCode.UP);
+      expect(getTreeNode(-1)).toHaveClass('rc-tree-treenode-active');
       checkKeyDownTrigger();
       checkActiveTrigger('child 2');
 
       // Arrow left: parent
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.LEFT });
-      expect(
-        wrapper
-          .find(InternalTreeNode)
-          .find('.rc-tree-treenode')
-          .first()
-          .hasClass('rc-tree-treenode-active'),
-      ).toBeTruthy();
+      keyDown(KeyCode.LEFT);
+      expect(getTreeNode(0)).toHaveClass('rc-tree-treenode-active');
       checkKeyDownTrigger();
       checkActiveTrigger('parent');
 
       // Arrow left: collapse
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.LEFT });
-      expect(
-        wrapper
-          .find(InternalTreeNode)
-          .find('.rc-tree-treenode')
-          .first()
-          .hasClass('rc-tree-treenode-active'),
-      ).toBeTruthy();
+      keyDown(KeyCode.LEFT);
+      expect(getTreeNode(0)).toHaveClass('rc-tree-treenode-active');
       expect(onExpand).toHaveBeenCalledWith(['child 1', 'child 2'], expect.anything());
       checkKeyDownTrigger();
 
       // Arrow right: expand
       onExpand.mockReset();
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.RIGHT });
-      expect(
-        wrapper
-          .find(InternalTreeNode)
-          .find('.rc-tree-treenode')
-          .first()
-          .hasClass('rc-tree-treenode-active'),
-      ).toBeTruthy();
+      keyDown(KeyCode.RIGHT);
+      expect(getTreeNode(0)).toHaveClass('rc-tree-treenode-active');
       expect(onExpand).toHaveBeenCalledWith(['child 1', 'child 2', 'parent'], expect.anything());
       checkKeyDownTrigger();
 
       // Arrow right: first child
       onExpand.mockReset();
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.RIGHT });
-      expect(
-        wrapper
-          .find(InternalTreeNode)
-          .find('.rc-tree-treenode')
-          .at(1)
-          .hasClass('rc-tree-treenode-active'),
-      ).toBeTruthy();
+      keyDown(KeyCode.RIGHT);
+      expect(getTreeNode(1)).toHaveClass('rc-tree-treenode-active');
       checkKeyDownTrigger();
       checkActiveTrigger('child 1');
 
       // SPACE: confirm
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.SPACE });
+      keyDown(KeyCode.SPACE);
       spaceCallback();
       checkKeyDownTrigger();
 
       // ENTER: confirm again
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.ENTER });
+      keyDown(KeyCode.ENTER);
       enterCallback();
       checkKeyDownTrigger();
 
       // Blur
-      wrapper.find('input').simulate('blur');
+      fireEvent.blur(container.querySelector('input'));
       expect(onBlur).toHaveBeenCalled();
 
       // null activeKey
-      wrapper.find('.rc-tree-treenode').first().simulate('mouseMove');
+      fireEvent.mouseMove(getTreeNode(0));
       checkActiveTrigger(null);
 
       for (let i = 0; i < 10; i += 1) {
-        wrapper.find('.rc-tree-treenode').first().simulate('mouseMove');
+        fireEvent.mouseMove(getTreeNode(0));
         expect(onActiveChange).not.toHaveBeenCalled();
       }
     }
@@ -183,34 +154,38 @@ describe('Tree Accessibility', () => {
     });
 
     it('not crash if not exist', () => {
-      const wrapper = mount(<Tree defaultExpandAll treeData={[]} />);
+      const { container } = render(<Tree defaultExpandAll treeData={[]} />);
 
-      wrapper.find('input').simulate('focus');
+      fireEvent.focus(container.querySelector('input'));
 
       // Arrow should not work
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.UP });
-      expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeFalsy();
+      fireEvent.keyDown(container.querySelector('input'), {
+        keyCode: KeyCode.UP,
+      });
+      expect(container.querySelector('.rc-tree-treenode-active')).toBeFalsy();
     });
 
     it('remove active if mouse hover', () => {
-      const wrapper = mount(<Tree defaultExpandAll treeData={[{ key: 'parent' }]} />);
+      const { container } = render(<Tree defaultExpandAll treeData={[{ key: 'parent' }]} />);
 
-      wrapper.find('input').simulate('focus');
+      fireEvent.focus(container.querySelector('input'));
 
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.UP });
-      expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeTruthy();
+      fireEvent.keyDown(container.querySelector('input'), {
+        keyCode: KeyCode.UP,
+      });
+      expect(container.querySelector('.rc-tree-treenode-active')).toBeTruthy();
 
       // Mouse move
-      wrapper.find('.rc-tree-treenode').at(1).simulate('mouseMove');
-      expect(wrapper.find(InternalTreeNode).find('.rc-tree-treenode-active').length).toBeFalsy();
+      fireEvent.mouseMove(container.querySelectorAll('.rc-tree-treenode')[1]);
+      expect(container.querySelector('.rc-tree-treenode-active')).toBeFalsy();
     });
 
     it('fieldNames should also work', () => {
       const onActiveChange = jest.fn();
       const onSelect = jest.fn();
 
-      const wrapper = mount(
-        <Tree
+      const { container } = render(
+        <Tree<FieldDataNode<{ value: string }>>
           defaultExpandAll
           treeData={[{ value: 'first' }, { value: 'second' }]}
           fieldNames={{ key: 'value' }}
@@ -219,27 +194,33 @@ describe('Tree Accessibility', () => {
         />,
       );
 
-      wrapper.find('input').simulate('focus');
+      fireEvent.focus(container.querySelector('input'));
 
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
+      fireEvent.keyDown(container.querySelector('input'), {
+        keyCode: KeyCode.DOWN,
+      });
       expect(onActiveChange).toHaveBeenCalledWith('first');
 
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.DOWN });
+      fireEvent.keyDown(container.querySelector('input'), {
+        keyCode: KeyCode.DOWN,
+      });
       expect(onActiveChange).toHaveBeenCalledWith('second');
 
-      wrapper.find('input').simulate('keyDown', { which: KeyCode.ENTER });
+      fireEvent.keyDown(container.querySelector('input'), {
+        keyCode: KeyCode.ENTER,
+      });
       expect(onSelect).toHaveBeenCalledWith(['second'], expect.anything());
     });
   });
 
   it('disabled should prevent keyboard', () => {
-    const wrapper = mount(<Tree disabled />);
-    expect(wrapper.find('input').props().disabled).toBeTruthy();
+    const { container } = render(<Tree disabled />);
+    expect(container.querySelector('input')).toHaveAttribute('disabled');
   });
 
   describe('activeKey in control', () => {
     it('basic', () => {
-      const wrapper = mount(
+      const { container, rerender } = render(
         <Tree
           treeData={[
             {
@@ -250,16 +231,25 @@ describe('Tree Accessibility', () => {
         />,
       );
 
-      expect(wrapper.exists('.rc-tree-treenode-active')).toBeFalsy();
+      expect(container.querySelector('.rc-tree-treenode-active')).toBeFalsy();
 
-      wrapper.setProps({ activeKey: 'parent' });
-      wrapper.update();
-      expect(wrapper.exists('.rc-tree-treenode-active')).toBeTruthy();
+      rerender(
+        <Tree
+          treeData={[
+            {
+              title: 'Parent',
+              key: 'parent',
+            },
+          ]}
+          activeKey="parent"
+        />,
+      );
+      expect(container.querySelector('.rc-tree-treenode-active')).toBeTruthy();
     });
 
     it('with fieldNames', () => {
-      const wrapper = mount(
-        <Tree
+      const { container, rerender } = render(
+        <Tree<FieldDataNode<{ title: string; value: string }>>
           fieldNames={{ key: 'value' }}
           treeData={[
             {
@@ -270,11 +260,21 @@ describe('Tree Accessibility', () => {
         />,
       );
 
-      expect(wrapper.exists('.rc-tree-treenode-active')).toBeFalsy();
+      expect(container.querySelector('.rc-tree-treenode-active')).toBeFalsy();
 
-      wrapper.setProps({ activeKey: 'parent' });
-      wrapper.update();
-      expect(wrapper.exists('.rc-tree-treenode-active')).toBeTruthy();
+      rerender(
+        <Tree<FieldDataNode<{ title: string; value: string }>>
+          fieldNames={{ key: 'value' }}
+          treeData={[
+            {
+              title: 'Parent',
+              value: 'parent',
+            },
+          ]}
+          activeKey="parent"
+        />,
+      );
+      expect(container.querySelector('.rc-tree-treenode-active')).toBeTruthy();
     });
   });
 });
diff --git a/tests/FieldNames.spec.tsx b/tests/FieldNames.spec.tsx
index ce4aa3df..e98efc8d 100644
--- a/tests/FieldNames.spec.tsx
+++ b/tests/FieldNames.spec.tsx
@@ -1,15 +1,15 @@
 /* eslint-disable no-undef, react/no-multi-comp */
 import React from 'react';
-import { mount } from 'enzyme';
+import { render, fireEvent } from '@testing-library/react';
 import Tree from '../src';
-import { InternalTreeNode } from '../src/TreeNode';
 import { spyConsole } from './util';
 
 describe('FieldNames', () => {
   spyConsole();
 
   it('customize fieldNames', () => {
-    const wrapper = mount(
+    const onSelect = jest.fn();
+    const { container } = render(
       <Tree
         treeData={
           [
@@ -29,24 +29,28 @@ describe('FieldNames', () => {
           key: 'myKey',
           children: 'myChildren',
         }}
+        onSelect={onSelect}
+        multiple
       />,
     );
 
     // Title
-    const titleList = wrapper
-      .find('.rc-tree-list-holder div.rc-tree-treenode')
-      .map(node => node.text());
+    const titleList = Array.from(
+      container.querySelectorAll('.rc-tree-list-holder div.rc-tree-treenode'),
+    ).map(node => node.textContent);
 
     expect(titleList).toEqual(['Title', 'Sub1', 'Sub2']);
 
     // Key
-    const keyList = wrapper.find(InternalTreeNode).map(node => node.props().eventKey);
+    container.querySelectorAll('.rc-tree-title').forEach(ele => {
+      fireEvent.click(ele);
+    });
 
-    expect(keyList).toEqual(['title', 'sub_1', 'sub_2']);
+    expect(onSelect).toHaveBeenCalledWith(['title', 'sub_1', 'sub_2'], expect.anything());
   });
 
   it('dynamic change fieldNames', () => {
-    const wrapper = mount(
+    const renderTree = (props?: any) => (
       <Tree
         defaultExpandAll
         treeData={[
@@ -65,58 +69,67 @@ describe('FieldNames', () => {
             ],
           },
         ]}
-      />,
+        {...props}
+      />
     );
 
+    const { container, rerender } = render(renderTree());
+
     // Title
     expect(
-      wrapper.find('.rc-tree-list-holder div.rc-tree-treenode').map(node => node.text()),
+      Array.from(container.querySelectorAll('.rc-tree-list-holder div.rc-tree-treenode')).map(
+        node => node.textContent,
+      ),
     ).toEqual(['Origin Title', 'Origin Sub 1', 'Origin Sub 2']);
 
     // Change it
-    wrapper.setProps({
-      treeData: [
-        {
-          myTitle: 'New Title',
-          myKey: 'parent',
-          myChildren: [
-            {
-              myTitle: 'New Sub 1',
-              myKey: 'sub_1',
-            },
-            {
-              myTitle: 'New Sub 2',
-              myKey: 'sub_2',
-            },
-          ],
-        },
-        {
-          myTitle: 'New Title 2',
-          myKey: 'parent2',
-          myChildren: [
-            {
-              myTitle: 'New Sub 3',
-              myKey: 'sub_3',
-            },
-          ],
+    rerender(
+      renderTree({
+        treeData: [
+          {
+            myTitle: 'New Title',
+            myKey: 'parent',
+            myChildren: [
+              {
+                myTitle: 'New Sub 1',
+                myKey: 'sub_1',
+              },
+              {
+                myTitle: 'New Sub 2',
+                myKey: 'sub_2',
+              },
+            ],
+          },
+          {
+            myTitle: 'New Title 2',
+            myKey: 'parent2',
+            myChildren: [
+              {
+                myTitle: 'New Sub 3',
+                myKey: 'sub_3',
+              },
+            ],
+          },
+        ],
+        fieldNames: {
+          title: 'myTitle',
+          key: 'myKey',
+          children: 'myChildren',
         },
-      ],
-      fieldNames: {
-        title: 'myTitle',
-        key: 'myKey',
-        children: 'myChildren',
-      },
-    });
+      }),
+    );
 
     expect(
-      wrapper.find('.rc-tree-list-holder div.rc-tree-treenode').map(node => node.text()),
+      Array.from(container.querySelectorAll('.rc-tree-list-holder div.rc-tree-treenode')).map(
+        node => node.textContent,
+      ),
     ).toEqual(['New Title', 'New Sub 1', 'New Sub 2', 'New Title 2']);
   });
 
   it('checkable should work', () => {
     const onCheck = jest.fn();
 
-    const wrapper = mount(
+    const { container } = render(
       <Tree
         checkable
         onCheck={onCheck}
@@ -136,13 +149,15 @@ describe('FieldNames', () => {
       />,
     );
 
-    wrapper.find('.rc-tree-checkbox').last().simulate('click');
+    fireEvent.click(container.querySelector('.rc-tree-checkbox'));
     expect(onCheck).toHaveBeenCalledWith(['parent', 'child'], expect.anything());
   });
 
   // Internal usage. Safe to remove
   it('fieldNames using _title', () => {
-    const wrapper = mount(
+    const onSelect = jest.fn();
+
+    const { container } = render(
       <Tree
         treeData={
           [
@@ -162,19 +177,23 @@ describe('FieldNames', () => {
           key: 'myKey',
           children: 'myChildren',
         }}
+        onSelect={onSelect}
+        multiple
       />,
     );
 
     // Title
-    const titleList = wrapper
-      .find('.rc-tree-list-holder div.rc-tree-treenode')
-      .map(node => node.text());
+    const titleList = Array.from(
+      container.querySelectorAll('.rc-tree-list-holder div.rc-tree-treenode'),
+    ).map(node => node.textContent);
 
     expect(titleList).toEqual(['Title', 'Sub1', 'Sub2']);
 
     // Key
-    const keyList = wrapper.find(InternalTreeNode).map(node => node.props().eventKey);
+    container.querySelectorAll('.rc-tree-title').forEach(ele => {
+      fireEvent.click(ele);
+    });
 
-    expect(keyList).toEqual(['title', 'sub_1', 'sub_2']);
+    expect(onSelect).toHaveBeenCalledWith(['title', 'sub_1', 'sub_2'], expect.anything());
   });
 });
diff --git a/tests/React18.spec.tsx b/tests/React18.spec.tsx
new file mode 100644
index 00000000..0e945d9d
--- /dev/null
+++ b/tests/React18.spec.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import Tree from '../src';
+
+describe('React 18', () => {
+  it('expand work', () => {
+    const onExpand = jest.fn();
+    const { container } = render(
+      <React.StrictMode>
+        <Tree
+          defaultExpandAll
+          onExpand={onExpand}
+          treeData={[
+            {
+              title: 'Parent',
+              key: 'parent',
+              children: [
+                {
+                  title: 'Child',
+                  key: 'child',
+                },
+              ],
+            },
+          ]}
+        />
+      </React.StrictMode>,
+    );
+
+    // All opened
+    expect(onExpand).not.toHaveBeenCalled();
+    expect(
+      container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode'),
+    ).toHaveLength(2);
+
+    // Collapse one
+    fireEvent.click(container.querySelector('.rc-tree-switcher_open'));
+    expect(onExpand).toHaveBeenCalled();
+    expect(
+      container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode'),
+    ).toHaveLength(1);
+  });
+
+  it('checkable work', () => {
+    const onCheck = jest.fn();
+    const { container } = render(
+      <React.StrictMode>
+        <Tree
+          defaultExpandAll
+          onCheck={onCheck}
+          checkable
+          treeData={[
+            {
+              title: 'Parent',
+              key: 'parent',
+            },
+          ]}
+        />
+      </React.StrictMode>,
+    );
+
+    expect(onCheck).not.toHaveBeenCalled();
+    expect(container.querySelector('.rc-tree-checkbox-checked')).toBeFalsy();
+
+    fireEvent.click(container.querySelector('.rc-tree-checkbox'));
+    expect(onCheck).toHaveBeenCalled();
+    expect(container.querySelector('.rc-tree-checkbox-checked')).toBeTruthy();
+  });
+});
diff --git a/tests/Tree.spec.js b/tests/Tree.spec.tsx
similarity index 57%
rename from tests/Tree.spec.js
rename to tests/Tree.spec.tsx
index 13eeba36..a112914f 100644
--- a/tests/Tree.spec.js
+++ b/tests/Tree.spec.tsx
@@ -1,21 +1,17 @@
 /* eslint-disable no-undef, react/no-multi-comp, no-console,
 react/no-unused-state, react/prop-types, no-return-assign */
 import React from 'react';
-import { render, mount } from 'enzyme';
-import { renderToJson } from 'enzyme-to-json';
+import { render, fireEvent } from '@testing-library/react';
 import { resetWarned } from 'rc-util/lib/warning';
 import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
-import VirtualList from 'rc-virtual-list';
 import Tree, { TreeNode } from '../src';
-import { InternalTreeNode } from '../src/TreeNode';
 import { objectMatcher, spyConsole, spyError } from './util';
-import { convertNodePropsToEventData } from '../src/utils/treeUtil';
 
-const OPEN_CLASSNAME = '.rc-tree-switcher_open';
-const CHECKED_CLASSNAME = '.rc-tree-checkbox-checked';
-const SELECTED_CLASSNAME = '.rc-tree-node-selected';
+const OPEN_CLASSNAME = 'rc-tree-switcher_open';
+const CHECKED_CLASSNAME = 'rc-tree-checkbox-checked';
+const SELECTED_CLASSNAME = 'rc-tree-node-selected';
 
-const delay = timeout =>
+const delay = (timeout = 0) =>
   new Promise(resolve => {
     setTimeout(resolve, timeout);
   });
@@ -28,7 +24,7 @@ describe('Tree Basic', () => {
   });
 
   it('renders correctly', () => {
-    const wrapper = render(
+    const { asFragment } = render(
       <Tree
         className="forTest"
         selectable
@@ -49,11 +45,11 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
-    expect(renderToJson(wrapper)).toMatchSnapshot();
+    expect(asFragment().firstChild).toMatchSnapshot();
   });
 
   it('switcherIcon = null, no render rc-tree-switcher null', () => {
-    const wrapper = mount(
+    const { container } = render(
       <Tree className="forTest" defaultExpandAll switcherIcon={() => false}>
         <TreeNode title="parent 1" key="0-0" className="spe">
           <TreeNode title="leaf 1" key="0-0-0" disabled>
@@ -64,51 +60,49 @@ describe('Tree Basic', () => {
         </TreeNode>
       </Tree>,
     );
-    wrapper.update();
-    expect(wrapper.find('.rc-tree-switcher').exists()).toBeFalsy();
+    expect(container.querySelector('.rc-tree-switcher')).toBeFalsy();
   });
 
   describe('expanded', () => {
     it('expands all keys', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree defaultExpandAll>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
         </Tree>,
       );
-      const switcher = wrapper.find('.rc-tree-switcher').first();
-      expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
+      expect(container.querySelector('.rc-tree-switcher')).toHaveClass(OPEN_CLASSNAME);
     });
 
     it('expands default expanded keys', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree defaultExpandedKeys={['0-0']}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
         </Tree>,
       );
-      const switcher = wrapper.find('.rc-tree-switcher').first();
-      expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
+      expect(container.querySelector('.rc-tree-switcher')).toHaveClass(OPEN_CLASSNAME);
     });
 
     it('controlled by expanded keys', () => {
-      const wrapper = mount(
-        <Tree expandedKeys={[]}>
+      const renderTree = (props?: any) => (
+        <Tree expandedKeys={[]} {...props}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
-        </Tree>,
+        </Tree>
       );
-      const getSwitcher = () => wrapper.find('.rc-tree-switcher').first();
-      expect(getSwitcher().is(OPEN_CLASSNAME)).toBe(false);
-      wrapper.setProps({ expandedKeys: ['0-0'] });
-      expect(getSwitcher().is(OPEN_CLASSNAME)).toBe(true);
+
+      const { container, rerender } = render(renderTree());
+      expect(container.querySelector(`.${OPEN_CLASSNAME}`)).toBeFalsy();
+      rerender(renderTree({ expandedKeys: ['0-0'] }));
+      expect(container.querySelector(`.${OPEN_CLASSNAME}`)).toBeTruthy();
     });
 
     it('expands parent node when child node is expanded', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree expandedKeys={['0-0-0']}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0">
@@ -118,8 +112,7 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      const switcher = wrapper.find('.rc-tree-switcher').first();
-      expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
+      expect(container.querySelector('.rc-tree-switcher')).toHaveClass(OPEN_CLASSNAME);
     });
 
     it('use treeData to expand the parent node when the parent node key type is numeric', () => {
@@ -131,14 +124,13 @@ describe('Tree Basic', () => {
         />
       );
 
-      const wrapper = mount(<Demo />);
+      const { container } = render(<Demo />);
 
-      const switcher = wrapper.find('.rc-tree-switcher').first();
-      expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
+      expect(container.querySelector('.rc-tree-switcher')).toHaveClass(OPEN_CLASSNAME);
     });
 
     it('does not expand parent node when autoExpandParent is false', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree expandedKeys={['0-0-0']} defaultExpandParent={false}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0">
@@ -147,33 +139,31 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      const switcher = wrapper.find('.rc-tree-switcher').first();
-      expect(switcher.is(OPEN_CLASSNAME)).toBe(false);
+      expect(container.querySelector(`.${OPEN_CLASSNAME}`)).toBeFalsy();
     });
 
     it('update to expand parent node with autoExpandParent', () => {
-      const wrapper = mount(
-        <Tree expandedKeys={['0-0-0']} defaultExpandParent={false}>
+      const renderTree = (props?: any) => (
+        <Tree expandedKeys={['0-0-0']} defaultExpandParent={false} {...props}>
           <TreeNode title="parent 1" checkable={false} key="0-0">
             <TreeNode title="leaf 1" key="0-0-0">
               <TreeNode title="leaf" key="0-0-0-0" />
             </TreeNode>
           </TreeNode>
-        </Tree>,
+        </Tree>
       );
-      let parentSwitcher = wrapper.find('.rc-tree-switcher').first();
-      expect(parentSwitcher.is(OPEN_CLASSNAME)).toBe(false);
+      const { container, rerender } = render(renderTree());
+      expect(container.querySelector(`.${OPEN_CLASSNAME}`)).toBeFalsy();
 
-      wrapper.setProps({ autoExpandParent: true });
+      rerender(renderTree({ autoExpandParent: true }));
 
-      parentSwitcher = wrapper.find('.rc-tree-switcher').first();
-      const childSwitcher = wrapper.find('.rc-tree-switcher').at(1);
-      expect(parentSwitcher.is(OPEN_CLASSNAME)).toBe(true);
-      expect(childSwitcher.is(OPEN_CLASSNAME)).toBe(true);
+      // All opened
+      expect(container.querySelectorAll('.rc-tree-switcher')[0]).toHaveClass(OPEN_CLASSNAME);
+      expect(container.querySelectorAll('.rc-tree-switcher')[1]).toHaveClass(OPEN_CLASSNAME);
     });
 
     it('skip only if disabled with autoExpandParent', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree expandedKeys={['0-0-0-0']} defaultExpandParent>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" disabled key="0-0-0">
@@ -183,33 +173,38 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      expect(wrapper.state().expandedKeys.sort()).toEqual(['0-0-0', '0-0-0-0']);
+      expect(
+        container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode'),
+      ).toHaveLength(1);
     });
 
     it('fires expand event', () => {
       const handleExpand = jest.fn();
-      const wrapper = mount(
+      const { container } = render(
         <Tree onExpand={handleExpand}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
         </Tree>,
       );
-      const switcher = wrapper.find('.rc-tree-switcher');
 
-      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
-      switcher.simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
       expect(handleExpand).toHaveBeenCalledWith(['0-0'], {
         expanded: true,
-        node,
+        node: expect.objectContaining({
+          key: '0-0',
+          title: 'parent 1',
+        }),
         nativeEvent: expect.anything(),
       });
 
-      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
-      switcher.simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
       expect(handleExpand).toHaveBeenCalledWith([], {
         expanded: false,
-        node,
+        node: expect.objectContaining({
+          key: '0-0',
+          title: 'parent 1',
+        }),
         nativeEvent: expect.anything(),
       });
     });
@@ -217,7 +212,7 @@ describe('Tree Basic', () => {
 
   describe('check', () => {
     it('basic render', () => {
-      const wrapper = render(
+      const { asFragment } = render(
         <Tree checkable defaultExpandAll>
           <TreeNode key="0-0">
             <TreeNode key="0-0-0" disabled />
@@ -226,25 +221,26 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      expect(wrapper).toMatchSnapshot();
+      expect(asFragment().firstChild).toMatchSnapshot();
     });
 
     it('checks default checked keys', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree checkable defaultCheckedKeys={['0-0']}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
         </Tree>,
       );
-      wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
-        expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
+      expect(container.querySelectorAll('.rc-tree-checkbox')).toHaveLength(2);
+      container.querySelectorAll('.rc-tree-checkbox').forEach(checkbox => {
+        expect(checkbox).toHaveClass(CHECKED_CLASSNAME);
       });
     });
 
     it("ignore disabled children when calculate parent's checked status", () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree checkable defaultCheckedKeys={['0-0-0']}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" disableCheckbox />
@@ -252,28 +248,26 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      const firstCheckboxClassNames = wrapper.find('.rc-tree-checkbox').instance().classList;
-      expect([].slice.call(firstCheckboxClassNames).includes(CHECKED_CLASSNAME.slice(1))).toBe(
-        false,
-      );
+      expect(container.querySelector(`.${CHECKED_CLASSNAME}`)).toBeFalsy();
     });
 
     it('controlled by checkedKeys', () => {
-      const wrapper = mount(
-        <Tree checkable checkedKeys={[]}>
+      const renderTree = (props?: any) => (
+        <Tree checkable checkedKeys={[]} {...props}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
-        </Tree>,
+        </Tree>
       );
-      const getCheckbox = () => wrapper.find('.rc-tree-checkbox');
-      expect(getCheckbox().is(CHECKED_CLASSNAME)).toBe(false);
-      wrapper.setProps({ checkedKeys: ['0-0'] });
-      expect(getCheckbox().is(CHECKED_CLASSNAME)).toBe(true);
+      const { container, rerender } = render(renderTree());
+      expect(container.querySelector(`.${CHECKED_CLASSNAME}`)).toBeFalsy();
+
+      rerender(renderTree({ checkedKeys: ['0-0'] }));
+      expect(container.querySelector(`.${CHECKED_CLASSNAME}`)).toBeTruthy();
     });
 
-    it('trurns parent node to checked when all children are checked', () => {
-      const wrapper = mount(
+    it('turn parent node to checked when all children are checked', () => {
+      const { container } = render(
         <Tree checkable>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
@@ -281,14 +275,15 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
-      wrapper.find('.rc-tree-checkbox').at(2).simulate('click');
-      expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
+
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[1]);
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[2]);
+      expect(container.querySelectorAll('.rc-tree-checkbox')[0]).toHaveClass(CHECKED_CLASSNAME);
     });
 
     it('turns parent node to half checked when child is checked', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree checkable>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
@@ -296,15 +291,16 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').last().simulate('click');
-      expect(wrapper.find('.rc-tree-checkbox').first().is('.rc-tree-checkbox-indeterminate')).toBe(
-        true,
+
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[2]);
+      expect(container.querySelectorAll('.rc-tree-checkbox')[0]).toHaveClass(
+        'rc-tree-checkbox-indeterminate',
       );
     });
 
     it('turns parent node to checked if it is half checked', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree checkable>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
@@ -312,30 +308,29 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      wrapper.find('.rc-tree-switcher').simulate('click');
-      wrapper.find('.rc-tree-checkbox').last().simulate('click');
-      wrapper.find('.rc-tree-checkbox').first().simulate('click');
-      wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
-        expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[2]);
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[0]);
+      container.querySelectorAll('.rc-tree-checkbox').forEach(ele => {
+        expect(ele).toHaveClass(CHECKED_CLASSNAME);
       });
     });
 
     it('fires check event', () => {
       const handleCheck = jest.fn();
-      const wrapper = mount(
+      const { container } = render(
         <Tree checkable onCheck={handleCheck}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
         </Tree>,
       );
-      wrapper.find('.rc-tree-switcher').simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
 
       const nodeData00 = { title: 'parent 1', key: '0-0' };
       const nodeData000 = { title: 'leaf 1', key: '0-0-0' };
 
-      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
-      wrapper.find('.rc-tree-checkbox').first().simulate('click');
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[0]);
       expect(handleCheck).toHaveBeenCalledWith(
         ['0-0', '0-0-0'],
         objectMatcher({
@@ -347,13 +342,15 @@ describe('Tree Basic', () => {
           ],
           event: 'check',
           halfCheckedKeys: [],
-          node,
+          node: {
+            title: 'parent 1',
+            key: '0-0',
+          },
           nativeEvent: {},
         }),
       );
 
-      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).last().props());
-      wrapper.find('.rc-tree-checkbox').last().simulate('click');
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[1]);
       expect(handleCheck).toHaveBeenCalledWith(
         [],
         objectMatcher({
@@ -362,7 +359,10 @@ describe('Tree Basic', () => {
           checkedNodesPositions: [],
           event: 'check',
           halfCheckedKeys: [],
-          node,
+          node: {
+            title: 'leaf 1',
+            key: '0-0-0',
+          },
           nativeEvent: {},
         }),
       );
@@ -370,51 +370,69 @@ describe('Tree Basic', () => {
 
     // https://github.com/react-component/tree/issues/117
     it('check works correctly after dragging children under another node', () => {
-      const wrapper = mount(
+      const renderTree = (children: React.ReactNode) => (
         <Tree defaultExpandAll checkable>
+          {children}
+        </Tree>
+      );
+      const { container, rerender } = render(
+        renderTree(
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
             <TreeNode title="leaf 2" key="0-0-1" />
-          </TreeNode>
-        </Tree>,
+          </TreeNode>,
+        ),
       );
-      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
-      wrapper.setProps({
-        children: (
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[1]);
+
+      rerender(
+        renderTree(
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 2" key="0-0-1">
               <TreeNode title="leaf 1" key="0-0-0" />
             </TreeNode>
-          </TreeNode>
+          </TreeNode>,
         ),
-      });
-      expect(() => wrapper.find('.rc-tree-checkbox').at(2).simulate('click')).not.toThrow();
+      );
+
+      expect(() => {
+        fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[2]);
+      }).not.toThrow();
     });
+
     // https://github.com/react-component/tree/issues/90
     it('check works correctly after adding children dynamically', () => {
-      const wrapper = mount(
+      const renderTree = (children: React.ReactNode) => (
         <Tree defaultExpandAll checkable>
+          {children}
+        </Tree>
+      );
+      const { container, rerender } = render(
+        renderTree(
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
-          </TreeNode>
-        </Tree>,
+          </TreeNode>,
+        ),
       );
-      wrapper.find('.rc-tree-checkbox').at(1).simulate('click');
-      wrapper.setProps({
-        children: (
+
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[1]);
+      rerender(
+        renderTree(
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
             <TreeNode title="leaf 2" key="0-0-1" />
-          </TreeNode>
+          </TreeNode>,
         ),
-      });
-      expect(() => wrapper.find('.rc-tree-checkbox').at(2).simulate('click')).not.toThrow();
+      );
+      expect(() => {
+        fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[2]);
+      }).not.toThrow();
     });
 
     // https://github.com/ant-design/ant-design/issues/7353
     it('check children after changing from children[disableCheckbox] from true to false', () => {
       let checkedKeys = null;
-      const mockHandleCheck = keys => (checkedKeys = keys);
+      const mockHandleCheck = (keys: React.Key[]) => (checkedKeys = keys);
       function Test({ disableCheckbox }) {
         return (
           <Tree checkable onCheck={mockHandleCheck}>
@@ -426,30 +444,35 @@ describe('Tree Basic', () => {
           </Tree>
         );
       }
-      const wrapper = mount(<Test disableCheckbox />);
-      wrapper.find('.rc-tree-checkbox').first().simulate('click');
+      const { container, rerender } = render(<Test disableCheckbox />);
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[0]);
       expect(checkedKeys).toEqual(['0-0']);
-      wrapper.setProps({ disableCheckbox: false });
-      wrapper.find('.rc-tree-checkbox').first().simulate('click').simulate('click');
+
+      rerender(<Test disableCheckbox={false} />);
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[0]);
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[0]);
       expect(checkedKeys).toEqual(['0-0', '0-0-1', '0-0-2', '0-0-3']);
     });
 
     it('check dynamic children when their parent is checked', () => {
-      function mockLoadData() {}
-      const wrapper = mount(
-        <Tree checkable defaultCheckedKeys={['0-0']} loadData={mockLoadData}>
-          <TreeNode title="parent 1" key="0-0" />
-        </Tree>,
+      const mockLoadData: any = () => new Promise(() => {});
+      const renderTree = (children?: React.ReactNode) => (
+        <Tree checkable defaultCheckedKeys={['0-0']} loadData={mockLoadData} expandedKeys={['0-0']}>
+          {children}
+        </Tree>
       );
-      wrapper.setProps({
-        children: (
+
+      const { container, rerender } = render(renderTree(<TreeNode title="parent 1" key="0-0" />));
+      rerender(
+        renderTree(
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
             <TreeNode title="leaf 2" key="0-0-1" />
-          </TreeNode>
+          </TreeNode>,
         ),
-      });
-      expect(wrapper.state('checkedKeys')).toEqual(['0-0', '0-0-0', '0-0-1']);
+      );
+
+      expect(container.querySelectorAll(`.${CHECKED_CLASSNAME}`)).toHaveLength(3);
     });
 
     // https://github.com/ant-design/ant-design/issues/10132
@@ -469,10 +492,10 @@ describe('Tree Basic', () => {
           );
         }
       }
-      const wrapper = mount(<Test />);
-      wrapper.find('.rc-tree-checkbox').first().simulate('click');
+      const { container } = render(<Test />);
+      fireEvent.click(container.querySelector('.rc-tree-checkbox'));
 
-      expect(renderToJson(wrapper.render())).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
     });
 
     describe('check after data ready', () => {
@@ -480,25 +503,24 @@ describe('Tree Basic', () => {
 
       it('works', () => {
         const checkedKeys = ['0-0-0'];
-        const wrapper = mount(<Tree checkable checkedKeys={checkedKeys} />);
+        const { container, rerender } = render(<Tree checkable checkedKeys={checkedKeys} />);
         expect(errorSpy()).toHaveBeenCalledWith("Warning: Tree missing follow keys: '0-0-0'");
 
-        wrapper.setProps({
-          expandedKeys: ['0-0'],
-          children: (
+        rerender(
+          <Tree checkable checkedKeys={checkedKeys} expandedKeys={['0-0']}>
             <TreeNode key="0-0" title="Light">
               <TreeNode key="0-0-0" title="Bamboo" />
             </TreeNode>
-          ),
-        });
+          </Tree>,
+        );
 
-        expect(wrapper.render()).toMatchSnapshot();
+        expect(container.firstChild).toMatchSnapshot();
       });
     });
 
     it('should ignore !checkable node', () => {
       const onCheck = jest.fn();
-      const wrapper = mount(
+      const { container } = render(
         <Tree checkable defaultExpandAll onCheck={onCheck}>
           <TreeNode key="0-0">
             <TreeNode key="0-0-0" checkable={false} />
@@ -507,24 +529,26 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      wrapper.find('.rc-tree-checkbox').last().simulate('click');
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[1]);
 
       expect(onCheck.mock.calls[0][0].sort()).toEqual(['0-0', '0-0-1']);
     });
 
     describe('strictly', () => {
       it('checks strictly', () => {
-        const wrapper = mount(
+        const { container } = render(
           <Tree checkable checkStrictly>
             <TreeNode title="parent 1" key="0-0">
               <TreeNode title="leaf 1" key="0-0-0" />
             </TreeNode>
           </Tree>,
         );
-        wrapper.find('.rc-tree-switcher').simulate('click');
-        wrapper.find('.rc-tree-checkbox').first().simulate('click');
-        expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
-        expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
+        fireEvent.click(container.querySelector('.rc-tree-switcher'));
+        fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[0]);
+        expect(container.querySelectorAll('.rc-tree-checkbox')[0]).toHaveClass(CHECKED_CLASSNAME);
+        expect(container.querySelectorAll('.rc-tree-checkbox')[1]).not.toHaveClass(
+          CHECKED_CLASSNAME,
+        );
       });
 
       describe('controlled mode', () => {
@@ -542,45 +566,55 @@ describe('Tree Basic', () => {
 
           render() {
             return (
-              <Tree
-                checkable
-                checkStrictly
-                checkedKeys={this.state.checkedKeys}
-                onCheck={this.handleCheck}
-              >
-                <TreeNode title="parent 1" key="0-0">
-                  <TreeNode title="leaf 1" key="0-0-0" />
-                </TreeNode>
-              </Tree>
+              <>
+                <Tree
+                  checkable
+                  checkStrictly
+                  checkedKeys={this.state.checkedKeys}
+                  onCheck={this.handleCheck}
+                >
+                  <TreeNode title="parent 1" key="0-0">
+                    <TreeNode title="leaf 1" key="0-0-0" />
+                  </TreeNode>
+                </Tree>
+                <button
+                  onClick={() => {
+                    this.setState({ checkedKeys: { checked: ['0-0', '0-0-0'], halfChecked: [] } });
+                  }}
+                />
+              </>
             );
           }
         }
 
         it('do not check children', () => {
-          const wrapper = mount(<App />);
-          wrapper.find('.rc-tree-switcher').simulate('click');
-          wrapper.find('.rc-tree-checkbox').first().simulate('click');
-          expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
-          expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
+          const { container } = render(<App />);
+          fireEvent.click(container.querySelector('.rc-tree-switcher'));
+          fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[0]);
+          expect(container.querySelectorAll('.rc-tree-checkbox')[0]).toHaveClass(CHECKED_CLASSNAME);
+          expect(container.querySelectorAll('.rc-tree-checkbox')[1]).not.toHaveClass(
+            CHECKED_CLASSNAME,
+          );
         });
 
         it('do not uncheck parent', () => {
-          const wrapper = mount(<App />);
-          wrapper.setState({
-            checkedKeys: { checked: ['0-0', '0-0-0'], halfChecked: [] },
-          });
-          wrapper.find('.rc-tree-switcher').simulate('click');
-          wrapper.find('.rc-tree-checkbox').last().simulate('click');
-          expect(wrapper.find('.rc-tree-checkbox').first().is(CHECKED_CLASSNAME)).toBe(true);
-          expect(wrapper.find('.rc-tree-checkbox').last().is(CHECKED_CLASSNAME)).toBe(false);
+          const { container } = render(<App />);
+          fireEvent.click(container.querySelector('button'));
+          fireEvent.click(container.querySelector('.rc-tree-switcher'));
+          fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[1]);
+          expect(container.querySelectorAll('.rc-tree-checkbox')[0]).toHaveClass(CHECKED_CLASSNAME);
+          expect(container.querySelectorAll('.rc-tree-checkbox')[1]).not.toHaveClass(
+            CHECKED_CLASSNAME,
+          );
         });
       });
     });
 
     // https://github.com/ant-design/ant-design/issues/6891
     it('should ignore disableCheckbox children items when check parent', () => {
-      const wrapper = mount(
-        <Tree checkable defaultExpandAll>
+      const onCheck = jest.fn();
+      const { container } = render(
+        <Tree checkable defaultExpandAll onCheck={onCheck}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="node" key="0-0-0" disableCheckbox>
               <TreeNode title="node" key="0-0-0-0" disableCheckbox />
@@ -592,22 +626,23 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      const firstNode = () => wrapper.find('.rc-tree-checkbox').first();
-      firstNode().simulate('click');
+      const firstNode = container.querySelectorAll('.rc-tree-checkbox')[0];
+      fireEvent.click(firstNode);
 
-      expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(wrapper.state().checkedKeys.slice().sort()).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
+      expect(firstNode).toHaveClass('rc-tree-checkbox-checked');
+      expect(firstNode).not.toHaveClass('rc-tree-checkbox-indeterminate');
+      expect(onCheck).toHaveBeenCalledWith(['0-0', '0-0-1', '0-0-2'], expect.anything());
 
-      firstNode().simulate('click');
-      expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(wrapper.state().checkedKeys).toEqual([]);
+      fireEvent.click(firstNode);
+      expect(firstNode).not.toHaveClass('rc-tree-checkbox-checked');
+      expect(firstNode).not.toHaveClass('rc-tree-checkbox-indeterminate');
+      expect(onCheck).toHaveBeenCalledWith([], expect.anything());
     });
 
     it('should ignore disabled children items when check parent', () => {
-      const wrapper = mount(
-        <Tree checkable defaultExpandAll>
+      const onCheck = jest.fn();
+      const { container } = render(
+        <Tree checkable defaultExpandAll onCheck={onCheck}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="node" key="0-0-0" disabled>
               <TreeNode title="node" key="0-0-0-0" disabled />
@@ -619,22 +654,22 @@ describe('Tree Basic', () => {
         </Tree>,
       );
 
-      const firstNode = () => wrapper.find(TreeNode).first().find('.rc-tree-checkbox').first();
+      const firstNode = container.querySelector('.rc-tree-checkbox');
 
-      firstNode().simulate('click');
-      expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(wrapper.state().checkedKeys.slice().sort()).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
+      fireEvent.click(firstNode);
+      expect(firstNode).toHaveClass('rc-tree-checkbox-checked');
+      expect(firstNode).not.toHaveClass('rc-tree-checkbox-indeterminate');
+      expect(onCheck).toHaveBeenCalledWith(['0-0', '0-0-1', '0-0-2'], expect.anything());
 
-      firstNode().simulate('click');
-      expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
-      expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(wrapper.state().checkedKeys).toEqual([]);
+      fireEvent.click(firstNode);
+      expect(firstNode).not.toHaveClass('rc-tree-checkbox-checked');
+      expect(firstNode).not.toHaveClass('rc-tree-checkbox-indeterminate');
+      expect(onCheck).toHaveBeenCalledWith([], expect.anything());
     });
 
     // https://github.com/react-component/tree/pull/106#issuecomment-316779889
     it('should render parent check state correctly', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree checkable defaultExpandAll>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="node" key="0-0-0">
@@ -646,76 +681,90 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      const getParent = () => wrapper.find(TreeNode).at(1).find('.rc-tree-checkbox').first();
-      const getNode = () => wrapper.find(TreeNode).at(2).find('.rc-tree-checkbox').first();
-
-      getNode().simulate('click');
-      expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(getParent().hasClass('rc-tree-checkbox-checked')).toBe(true);
-      getNode().simulate('click');
-      expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
-      expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
-      expect(getParent().hasClass('rc-tree-checkbox-checked')).toBe(false);
+      const parent = container.querySelectorAll('.rc-tree-checkbox')[1];
+      const node = container.querySelectorAll('.rc-tree-checkbox')[2];
+
+      fireEvent.click(node);
+      expect(node).toHaveClass('rc-tree-checkbox-checked');
+      expect(parent).not.toHaveClass('rc-tree-checkbox-indeterminate');
+      expect(parent).toHaveClass('rc-tree-checkbox-checked');
+
+      fireEvent.click(node);
+      expect(node).not.toHaveClass('rc-tree-checkbox-checked');
+      expect(parent).not.toHaveClass('rc-tree-checkbox-indeterminate');
+      expect(parent).not.toHaveClass('rc-tree-checkbox-checked');
     });
   });
 
   describe('select', () => {
     it('selects default selected keys', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree selectable defaultSelectedKeys={['0-0']}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
         </Tree>,
       );
-      expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(true);
+      expect(container.querySelector('.rc-tree-node-content-wrapper')).toHaveClass(
+        SELECTED_CLASSNAME,
+      );
     });
 
     it('controlled by selectedKeys', () => {
-      const wrapper = mount(
-        <Tree selectable selectedKeys={[]}>
+      const renderTree = (props?: any) => (
+        <Tree selectable selectedKeys={[]} {...props}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
-        </Tree>,
+        </Tree>
+      );
+
+      const { container, rerender } = render(renderTree());
+      expect(container.querySelector('.rc-tree-node-content-wrapper')).not.toHaveClass(
+        SELECTED_CLASSNAME,
+      );
+
+      rerender(renderTree({ selectedKeys: ['0-0'] }));
+      expect(container.querySelector('.rc-tree-node-content-wrapper')).toHaveClass(
+        SELECTED_CLASSNAME,
       );
-      expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(false);
-      wrapper.setProps({ selectedKeys: ['0-0'] });
-      expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(true);
     });
 
     it('fires select event', () => {
       const handleSelect = jest.fn();
-      const wrapper = mount(
+      const { container } = render(
         <Tree selectable onSelect={handleSelect}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
         </Tree>,
       );
-      const nodeContent = wrapper.find('.rc-tree-node-content-wrapper');
+      const nodeContent = container.querySelector('.rc-tree-node-content-wrapper');
 
-      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
-      nodeContent.simulate('click');
+      fireEvent.click(nodeContent);
       expect(handleSelect).toHaveBeenCalledWith(
         ['0-0'],
         objectMatcher({
           event: 'select',
-          node,
+          node: {
+            title: 'parent 1',
+            key: '0-0',
+          },
           selected: true,
           selectedNodes: [{ title: 'parent 1', key: '0-0' }],
           nativeEvent: {},
         }),
       );
 
-      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
-      nodeContent.simulate('click');
+      fireEvent.click(nodeContent);
       expect(handleSelect).toHaveBeenCalledWith(
         [],
         objectMatcher({
           event: 'select',
-          node,
+          node: {
+            title: 'parent 1',
+            key: '0-0',
+          },
           selected: false,
           selectedNodes: [],
           nativeEvent: {},
@@ -727,20 +776,19 @@ describe('Tree Basic', () => {
   describe('checkable but not selectable', () => {
     it('fires check event when click on TreeNode', () => {
       const handleCheck = jest.fn();
-      const wrapper = mount(
+      const { container } = render(
         <Tree checkable selectable={false} onCheck={handleCheck}>
           <TreeNode title="parent 1" key="0-0">
             <TreeNode title="leaf 1" key="0-0-0" />
           </TreeNode>
         </Tree>,
       );
-      wrapper.find('.rc-tree-switcher').simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
 
       const dataNode1 = { title: 'parent 1', key: '0-0' };
       const dataNode2 = { title: 'leaf 1', key: '0-0-0' };
 
-      let node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).first().props());
-      wrapper.find('.rc-tree-node-content-wrapper').first().simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-node-content-wrapper'));
       expect(handleCheck).toHaveBeenCalledWith(
         ['0-0', '0-0-0'],
         objectMatcher({
@@ -752,13 +800,12 @@ describe('Tree Basic', () => {
           ],
           event: 'check',
           halfCheckedKeys: [],
-          node,
+          node: { title: 'parent 1', key: '0-0' },
           nativeEvent: {},
         }),
       );
 
-      node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).last().props());
-      wrapper.find('.rc-tree-node-content-wrapper').last().simulate('click');
+      fireEvent.click(container.querySelectorAll('.rc-tree-node-content-wrapper')[1]);
       expect(handleCheck).toHaveBeenCalledWith(
         [],
         objectMatcher({
@@ -767,7 +814,10 @@ describe('Tree Basic', () => {
           checkedNodesPositions: [],
           event: 'check',
           halfCheckedKeys: [],
-          node,
+          node: {
+            title: 'leaf 1',
+            key: '0-0-0',
+          },
           nativeEvent: {},
         }),
       );
@@ -776,64 +826,78 @@ describe('Tree Basic', () => {
 
   it('fires rightClick event', () => {
     const handleRightClick = jest.fn();
-    const wrapper = mount(
+    const { container } = render(
       <Tree onRightClick={handleRightClick}>
         <TreeNode title="parent 1" key="0-0">
           <TreeNode title="leaf 1" key="0-0-0" />
         </TreeNode>
       </Tree>,
     );
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
-    wrapper.find('.rc-tree-node-content-wrapper').simulate('contextMenu');
-    expect(handleRightClick.mock.calls[0][0].node).toEqual(node);
+    fireEvent.contextMenu(container.querySelector('.rc-tree-node-content-wrapper'));
+    expect(handleRightClick.mock.calls[0][0].node).toEqual(
+      expect.objectContaining({
+        title: 'parent 1',
+        key: '0-0',
+      }),
+    );
   });
 
   it('fires rightClick should not change selected item', () => {
-    const handleRightClick = jest.fn();
-    const wrapper = mount(
-      <Tree onRightClick={handleRightClick}>
+    const onRightClick = jest.fn();
+    const onSelect = jest.fn();
+    const { container } = render(
+      <Tree onRightClick={onRightClick} selectedKeys={['0-0-0']} onSelect={onSelect}>
         <TreeNode title="parent 1" key="0-0">
           <TreeNode title="leaf 1" key="0-0-0" />
         </TreeNode>
       </Tree>,
     );
-    wrapper.find('.rc-tree-node-content-wrapper').simulate('contextMenu');
-    expect(wrapper.state().selectedKeys.length).toBe(0);
+    fireEvent.contextMenu(container.querySelector('.rc-tree-node-content-wrapper'));
+    expect(onRightClick).toHaveBeenCalled();
+    expect(onSelect).not.toHaveBeenCalled();
+    expect(container.querySelector('.rc-tree-node-selected')).toBeFalsy();
   });
 
   it('fires mouseEnter events', () => {
     const handleMouseEnter = jest.fn();
-    const wrapper = mount(
+    const { container } = render(
       <Tree onMouseEnter={handleMouseEnter}>
         <TreeNode title="parent 1" key="0-0">
           <TreeNode title="leaf 1" key="0-0-0" />
         </TreeNode>
       </Tree>,
     );
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
-    wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseEnter');
-    expect(handleMouseEnter.mock.calls[0][0].node).toEqual(node);
+    fireEvent.mouseEnter(container.querySelector('.rc-tree-node-content-wrapper'));
+    expect(handleMouseEnter.mock.calls[0][0].node).toEqual(
+      expect.objectContaining({
+        key: '0-0',
+      }),
+    );
   });
 
   it('fires mouseLeave events', () => {
     const handleMouseLeave = jest.fn();
-    const wrapper = mount(
+    const { container } = render(
       <Tree onMouseLeave={handleMouseLeave}>
         <TreeNode title="parent 1" key="0-0">
           <TreeNode title="leaf 1" key="0-0-0" />
         </TreeNode>
       </Tree>,
     );
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
-    wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseLeave');
-    expect(handleMouseLeave.mock.calls[0][0].node).toEqual(node);
+
+    fireEvent.mouseLeave(container.querySelector('.rc-tree-node-content-wrapper'));
+    expect(handleMouseLeave.mock.calls[0][0].node).toEqual(
+      expect.objectContaining({
+        key: '0-0',
+      }),
+    );
   });
 
   it('filters nodes', () => {
     function filterTreeNode(treeNode) {
       return treeNode.title.indexOf('parent') > -1;
     }
-    const wrapper = mount(
+    const { container } = render(
       <Tree filterTreeNode={filterTreeNode}>
         <TreeNode title="parent 1" key="0-0">
           <TreeNode title="leaf 1" key="0-0-0">
@@ -845,26 +909,29 @@ describe('Tree Basic', () => {
       </Tree>,
     );
 
-    expect(wrapper.find(InternalTreeNode).find('div').is('.filter-node')).toBe(true);
+    expect(container.querySelector('.filter-node')).toBeTruthy();
   });
 
   it('loads nodes asynchronously', () => {
     const then = jest.fn(() => Promise.resolve());
-    const loadData = jest.fn(() => ({ then }));
-    const wrapper = mount(
+    const loadData: any = jest.fn(() => ({ then }));
+    const { container } = render(
       <Tree loadData={loadData}>
         <TreeNode title="parent 1" key="0-0" />
       </Tree>,
     );
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
-    wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(loadData).toHaveBeenCalledWith(node);
+    fireEvent.click(container.querySelector('.rc-tree-switcher'));
+    expect(loadData).toHaveBeenCalledWith(
+      expect.objectContaining({
+        key: '0-0',
+      }),
+    );
     expect(then).toHaveBeenCalled();
   });
 
   it('renders without errors', () => {
     expect(() => {
-      mount(
+      render(
         <Tree>
           {[0, 1].map(i => (
             <TreeNode title={i} key={i}>
@@ -880,7 +947,7 @@ describe('Tree Basic', () => {
   });
 
   it('renders opaque children correctly', () => {
-    const wrapper = render(
+    const { asFragment } = render(
       <Tree>
         <TreeNode title="0" key="0">
           {[1, 2].map(i => (
@@ -891,21 +958,21 @@ describe('Tree Basic', () => {
       </Tree>,
     );
 
-    expect(renderToJson(wrapper)).toMatchSnapshot();
+    expect(asFragment().firstChild).toMatchSnapshot();
   });
 
   describe('ignore illegal node as Tree children', () => {
     const errorSpy = spyError();
 
     it('Direct TreeNode', () => {
-      const wrapper = mount(
+      const { asFragment } = render(
         <Tree defaultExpandAll>
           <TreeNode key="00" title="00" />
           <span>Hide Me</span>
           <TreeNode key="02" title="02" />
         </Tree>,
       );
-      expect(wrapper.render()).toMatchSnapshot();
+      expect(asFragment().firstChild).toMatchSnapshot();
       expect(errorSpy()).toHaveBeenCalledWith(
         'Warning: Tree/TreeNode can only accept TreeNode as children.',
       );
@@ -913,7 +980,7 @@ describe('Tree Basic', () => {
 
     it('Sub TreeNode', () => {
       resetWarned();
-      const wrapper = mount(
+      const { asFragment } = render(
         <Tree defaultExpandAll>
           <TreeNode key="00" title="00" />
           <TreeNode key="01" title="01">
@@ -923,7 +990,7 @@ describe('Tree Basic', () => {
           </TreeNode>
         </Tree>,
       );
-      expect(wrapper.render()).toMatchSnapshot();
+      expect(asFragment().firstChild).toMatchSnapshot();
       expect(errorSpy()).toHaveBeenCalledWith(
         'Warning: Tree/TreeNode can only accept TreeNode as children.',
       );
@@ -934,7 +1001,7 @@ describe('Tree Basic', () => {
     const onCheck = jest.fn();
 
     const Demo = () => {
-      const [checkedKeys, setCheckedKeys] = React.useState([]);
+      const [checkedKeys, setCheckedKeys] = React.useState<any>([]);
 
       return (
         <Tree
@@ -950,19 +1017,18 @@ describe('Tree Basic', () => {
       );
     };
 
-    const wrapper = mount(<Demo />);
-    wrapper.update();
+    const { container } = render(<Demo />);
 
-    wrapper.find('.rc-tree-checkbox').first().simulate('click');
+    fireEvent.click(container.querySelector('.rc-tree-checkbox'));
     expect(onCheck).toHaveBeenCalledWith([11, 22]);
 
     onCheck.mockReset();
-    wrapper.find('.rc-tree-checkbox').last().simulate('click');
+    fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[1]);
     expect(onCheck).toHaveBeenCalledWith([]);
   });
 
   describe('scrollTo should work', () => {
-    let domSpy;
+    let domSpy: ReturnType<typeof spyElementPrototypes>;
     let called = false;
 
     beforeAll(() => {
@@ -985,9 +1051,10 @@ describe('Tree Basic', () => {
 
     it('work', () => {
       jest.useFakeTimers();
-      const wrapper = mount(<Tree />);
+      const treeRef = React.createRef<any>();
+      render(<Tree ref={treeRef} />);
 
-      wrapper.instance().scrollTo({ key: 'light', align: 'top' });
+      treeRef.current.scrollTo({ key: 'light', align: 'top' });
       jest.runAllTimers();
 
       expect(called).toBeTruthy();
@@ -996,7 +1063,7 @@ describe('Tree Basic', () => {
   });
 
   it('not crash if expandedKeys is null', () => {
-    mount(
+    render(
       <Tree expandedKeys={null}>
         <TreeNode key="test" title="alive" />
       </Tree>,
@@ -1004,32 +1071,36 @@ describe('Tree Basic', () => {
   });
 
   it('not crash if expandedKeys change to undefined', () => {
-    const wrapper = mount(
-      <Tree expandedKeys={['light']}>
+    const renderTree = (props?: any) => (
+      <Tree expandedKeys={['light']} {...props}>
         <TreeNode key="light" title="light">
           <TreeNode key="bamboo" title="bamboo" />
         </TreeNode>
-      </Tree>,
+      </Tree>
     );
-    const { data } = wrapper.find(VirtualList).props();
-    expect(data).toHaveLength(2);
+    const { container, rerender } = render(renderTree());
+    expect(
+      container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode'),
+    ).toHaveLength(2);
 
-    wrapper.setProps({ expandedKeys: undefined });
+    rerender(renderTree({ expandedKeys: undefined }));
 
     // Click should not crash
-    wrapper.find('.rc-tree-switcher').first().simulate('click');
+    fireEvent.click(container.querySelector('.rc-tree-switcher'));
   });
 
   it('controlled should block expanded', () => {
-    const wrapper = mount(
+    const { container } = render(
       <Tree
         expandedKeys={['1']}
         treeData={[{ key: '1', title: 1, children: [{ key: '2', title: 2 }] }]}
       />,
     );
 
-    wrapper.find('.rc-tree-switcher').first().simulate('click');
-    expect(wrapper.find('List').props().data).toHaveLength(2);
+    fireEvent.click(container.querySelector('.rc-tree-switcher'));
+    expect(
+      container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode'),
+    ).toHaveLength(2);
   });
 
   it('support virtual', () => {
@@ -1041,51 +1112,50 @@ describe('Tree Basic', () => {
       });
     }
 
-    const wrapper = mount(<Tree itemHeight={10} height={100} treeData={data} virtual={false} />);
+    const { container } = render(
+      <Tree itemHeight={10} height={100} treeData={data} virtual={false} />,
+    );
 
-    expect(wrapper.find('List').props().virtual).toBe(false);
+    expect(
+      container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode'),
+    ).toHaveLength(99);
   });
 
   // https://github.com/ant-design/ant-design/issues/28349
   it('should not trigger expend when loading data', () => {
     const then = jest.fn(() => Promise.resolve());
-    const loadData = jest.fn(() => ({ then }));
+    const loadData: any = jest.fn(() => ({ then }));
     const onExpand = jest.fn();
-    const wrapper = mount(
+    const { container } = render(
       <Tree loadData={loadData} onExpand={onExpand}>
         <TreeNode title="parent 1" key="0-0" />
       </Tree>,
     );
 
     // trigger click to expand node
-    wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(wrapper.find('.rc-tree-switcher').first().is(OPEN_CLASSNAME)).toBe(true);
+    fireEvent.click(container.querySelector('.rc-tree-switcher'));
+    expect(container.querySelector('.rc-tree-switcher')).toHaveClass(OPEN_CLASSNAME);
     expect(onExpand).toBeCalled();
 
     // click again
     onExpand.mockReset();
-    wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(wrapper.find('.rc-tree-switcher').first().is(OPEN_CLASSNAME)).toBe(true);
+    fireEvent.click(container.querySelector('.rc-tree-switcher'));
+    expect(container.querySelector('.rc-tree-switcher')).toHaveClass(OPEN_CLASSNAME);
     expect(onExpand).not.toHaveBeenCalled();
   });
 
   // https://github.com/ant-design/ant-design/issues/31141
   it('should restore tree node loading and expand status when loadData fail', async () => {
     const loadData = () => new Promise((_, reject) => setTimeout(reject));
-    const wrapper = mount(
+    const { container } = render(
       <Tree loadData={loadData}>
         <TreeNode title="parent 1" key="0-0" />
       </Tree>,
     );
-    // trigger click to expand node
-    wrapper.find('.rc-tree-switcher').simulate('click');
-    expect(
-      wrapper.find('.rc-tree-switcher').getDOMNode().className.includes('rc-tree-switcher_open'),
-    ).toBe(true);
-    await delay();
-    expect(
-      wrapper.find('.rc-tree-switcher').getDOMNode().className.includes('rc-tree-switcher_close'),
-    ).toBe(true);
+    fireEvent.click(container.querySelector('.rc-tree-switcher'));
+    expect(container.querySelector('.rc-tree-switcher')).toHaveClass('rc-tree-switcher_open');
+    await delay(100);
+    expect(container.querySelector('.rc-tree-switcher')).toHaveClass('rc-tree-switcher_close');
   });
 
   // https://github.com/ant-design/ant-design/issues/31141
@@ -1098,10 +1168,10 @@ describe('Tree Basic', () => {
         title: i,
       });
     }
-    const wrapper = mount(
+    const { container } = render(
       <Tree itemHeight={10} height={100} treeData={data} onScroll={onScroll} />,
     );
-    wrapper.find('.rc-tree-list-holder').simulate('scroll');
+    fireEvent.scroll(container.querySelector('.rc-tree-list-holder'));
     expect(onScroll).toBeCalled();
   });
 
@@ -1113,12 +1183,12 @@ describe('Tree Basic', () => {
         title: i,
       });
     }
-    const tree = mount(
+    const { container } = render(
       <Tree treeData={data} rootClassName="root-tree" rootStyle={{ backgroundColor: 'cyan' }} />,
     );
 
-    expect(tree.render()).toMatchSnapshot();
-    expect(tree.getDOMNode().className).toContain('root-tree');
-    expect(tree.getDOMNode().style.backgroundColor).toBe('cyan');
+    expect(container.firstChild).toMatchSnapshot();
+    expect(container.firstChild).toHaveClass('root-tree');
+    expect(container.firstChild).toHaveStyle({ backgroundColor: 'cyan' });
   });
 });
diff --git a/tests/TreeDraggable.spec.js b/tests/TreeDraggable.spec.js
deleted file mode 100644
index a4558adf..00000000
--- a/tests/TreeDraggable.spec.js
+++ /dev/null
@@ -1,798 +0,0 @@
-/* eslint-disable no-undef, react/no-multi-comp, no-console,
-react/no-unused-state, react/prop-types, no-return-assign */
-import React from 'react';
-import { mount } from 'enzyme';
-import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
-import Tree, { TreeNode } from '../src';
-import { InternalTreeNode } from '../src/TreeNode';
-import { spyConsole } from './util';
-import { convertNodePropsToEventData } from '../src/utils/treeUtil';
-
-const delay = timeout =>
-  new Promise(resolve => {
-    setTimeout(resolve, timeout);
-  });
-
-describe('Tree Draggable', () => {
-  spyConsole();
-
-  function createTree(props) {
-    return (
-      <Tree draggable defaultExpandAll {...props}>
-        <TreeNode title="parent 1" key="0-0">
-          <TreeNode className="dragTarget" title="leaf" key="0-0-0-0" />
-          <TreeNode className="dropTarget" title="leaf" key="0-0-0-1" />
-        </TreeNode>
-      </Tree>
-    );
-  }
-
-  it('fires dragStart event', () => {
-    const onDragStart = jest.fn();
-    const wrapper = mount(createTree({ onDragStart }));
-    const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
-    treeNode.simulate('dragStart');
-    const event = onDragStart.mock.calls[0][0];
-    expect(event.node).toEqual(node);
-  });
-
-  it('fires dragEnter event', async () => {
-    const onDragEnter = jest.fn();
-    const wrapper = mount(createTree({ onDragEnter }));
-
-    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-
-    // Not trigger self
-    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
-    await delay(900);
-    expect(onDragEnter).not.toHaveBeenCalled();
-
-    wrapper.find('.dropTarget').at(0).simulate('dragEnter');
-    expect(onDragEnter).toHaveBeenCalled();
-
-    await delay(900);
-    wrapper.update();
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-    const event = onDragEnter.mock.calls[0][0];
-    expect(event.node.key).toEqual(node.key);
-    expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
-    expect(onDragEnter).toHaveBeenCalledTimes(1);
-  });
-
-  it('fires dragOver event', () => {
-    const onDragOver = jest.fn();
-    const wrapper = mount(createTree({ onDragOver }));
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-    wrapper.find('.dropTarget').at(0).simulate('dragOver');
-    const event = onDragOver.mock.calls[0][0];
-    expect(event.node).toEqual(node);
-  });
-
-  it('fires dragLeave event', () => {
-    const onDragLeave = jest.fn();
-    const wrapper = mount(createTree({ onDragLeave }));
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-    wrapper.find('.dropTarget').at(0).simulate('dragLeave');
-    const event = onDragLeave.mock.calls[0][0];
-    expect(event.node).toEqual(node);
-  });
-
-  it('fires drop event', () => {
-    const onDrop = jest.fn();
-    const wrapper = mount(createTree({ onDrop }));
-    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-    wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
-    wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
-    const dropNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(2).props());
-    const dragNode = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
-    wrapper.find('.dropTarget').at(0).simulate('drop');
-    const event = onDrop.mock.calls[0][0];
-    expect(event.node).toEqual(dropNode);
-    expect(event.dragNode).toEqual(dragNode);
-    expect(event.dragNodesKeys).toEqual(['0-0-0-0']);
-  });
-
-  it('fires dropEnd event', () => {
-    const onDragEnd = jest.fn();
-    const wrapper = mount(createTree({ onDragEnd }));
-    const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).at(1).props());
-    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
-    const event = onDragEnd.mock.calls[0][0];
-    expect(event.node).toEqual(node);
-  });
-
-  it('do not throw error when drag into another non-drag-able tree', () => {
-    const wrapper = mount(
-      <div>
-        {createTree()}
-        {createTree({ draggable: false })}
-      </div>,
-    );
-
-    const dragTree = wrapper.find(Tree).at(0);
-    const normalTree = wrapper.find(Tree).at(1);
-
-    dragTree.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-    normalTree.find('.dropTarget').at(0).simulate('dragEnter');
-    normalTree.find('.dropTarget').at(0).simulate('dragOver');
-    normalTree.find('.dropTarget').at(0).simulate('drop');
-  });
-
-  describe('full steps', () => {
-    function dropTarget(targetSelector) {
-      return new Promise(resolve => {
-        const wrapper = mount(
-          <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
-            <TreeNode key="0-0" className="dragTarget">
-              <TreeNode key="0-0-0" className="dragTargetChild" />
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0" />
-            </TreeNode>
-            <TreeNode key="0-2" className="dropTarget">
-              <TreeNode key="0-2-0" />
-            </TreeNode>
-          </Tree>,
-        );
-
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-
-        // 1. Move into target (first in the middle of the node)
-        wrapper.find(targetSelector).simulate('dragEnter', { clientY: 10 });
-        setTimeout(() => {
-          wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
-
-          // 2. Move out of target
-          wrapper.find(targetSelector).simulate('dragLeave');
-
-          // 3. Move in again
-          wrapper.find(targetSelector).simulate('dragEnter', { clientY: 0 });
-          setTimeout(() => {
-            wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
-            // 4. Drop
-            wrapper.find(targetSelector).simulate('drop');
-            wrapper.find('div.dragTarget').simulate('dragEnd');
-
-            resolve();
-          }, 1000);
-        }, 10);
-      });
-    }
-    let domSpy;
-    beforeEach(() => {
-      domSpy = spyElementPrototypes(HTMLElement, {
-        offsetWidth: {
-          get() {
-            return 24;
-          },
-        },
-        getBoundingClientRect: jest.fn(() => ({
-          width: 100,
-          height: 20,
-          top: 0,
-          left: 0,
-          bottom: 20,
-          right: 100,
-        })),
-      });
-      // Object.defineProperties(window.HTMLElement.prototype, {
-      //   // mock indent as 24
-      //   // no need for clearing it, since jest make each file a independent env
-      //   offsetWidth: {
-      //     get() {
-      //       return 24;
-      //     },
-      //   },
-      // });
-    });
-
-    afterEach(() => {
-      domSpy.mockRestore();
-    });
-
-    it('self', () => dropTarget('div.dragTarget'));
-
-    it('target', () => dropTarget('div.dropTarget'));
-  });
-
-  describe('new drop logic', () => {
-    let domSpy;
-    beforeEach(() => {
-      domSpy = spyElementPrototypes(HTMLElement, {
-        getBoundingClientRect: () => ({
-          width: 100,
-          height: 20,
-          top: 0,
-          left: 0,
-          bottom: 20,
-          right: 100,
-        }),
-        offsetWidth: {
-          get() {
-            return 24;
-          },
-        },
-      });
-    });
-    afterEach(() => {
-      domSpy.mockRestore();
-    });
-    ['ltr', 'rtl'].forEach(dir => {
-      const base = dir === 'ltr' ? 1 : -1;
-      it('not allow cross level dnd on expanded nodes', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable onDrop={onDrop} direction={dir} expandedKeys={['0-0', '0-1', '0-1-0']}>
-            <TreeNode key="0-0">
-              <TreeNode key="0-0-0" className="dropTarget1">
-                <TreeNode key="0-0-0-0" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0" className="dropTarget2">
-                <TreeNode key="0-1-0-0" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" className="dragTarget" />
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('drop');
-        // insert after 0-0, since 0-0-0 is not expanded
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('drop');
-        // insert into 0-1-0, since it is expanded, do not allow cross level dnd
-        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0');
-        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(0);
-      });
-      it('allowDrop all nodes', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" />
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
-        // (in ltr) drag from right to left, should be insert after, so drop position is 1
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 600,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 600,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
-      });
-      it('allowDrop no node', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop} allowDrop={() => false} direction={dir}>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0" className="dropTargetParent">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" />
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
-        // not allow any dropPosition except 0 on expanded node
-        expect(onDrop).not.toHaveBeenCalled();
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 600,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 600,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-      });
-      it('drop to top half of first node', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
-              </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 400,
-          clientY: 0,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 400,
-          clientY: -1000,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
-      });
-      it('can drop on its direct parent', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
-              </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).toHaveBeenCalled();
-      });
-      it('cover window dragend & componentWillUnmount', () => {
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll direction={dir}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
-              </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        window.dispatchEvent(new Event('dragend'));
-        expect(wrapper.instance().state.draggingNodeKey).toBeFalsy();
-        wrapper.unmount();
-      });
-      it('dragover first half of non-first child', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
-              </TreeNode>
-              <TreeNode key="0-0-1" />
-              <TreeNode key="0-0-2" className="dropTarget" />
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 500,
-          clientY: 1,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 500,
-          clientY: 1,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
-      });
-      it('dragover self', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
-              </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 400,
-          clientY: 500,
-        });
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 600,
-          clientY: 500,
-        });
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 600,
-          clientY: 500,
-        });
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 600,
-          clientY: 500,
-        });
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-      });
-      it('not allowDrop on node which has children', () => {
-        const onDrop = jest.fn();
-        const allowDrop = ({ dropNode, dropPosition }) => {
-          if (!dropNode.children) {
-            if (dropPosition === 0) return false;
-          }
-          return true;
-        };
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-0" className="dragTarget">
-              <TreeNode key="0-0-0" className="dragTargetChild" />
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" />
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
-        // (in ltr) drag from right to left, should be insert after, so drop position is 1
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 500,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 550,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 600,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 600,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
-      });
-      it('allowDrop should pass dragNode and dropNode', () => {
-        const onDrop = jest.fn();
-        const allowDrop = jest.fn();
-        const wrapper = mount(
-          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-0" className="dragTarget">
-              <TreeNode key="0-0-0" className="dragTargetChild" />
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
-              </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" />
-          </Tree>,
-        );
-        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-          clientX: base * 500,
-          clientY: 500,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-          clientX: base * 400,
-          clientY: 600,
-        });
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(allowDrop.mock.calls[0][0].dragNode.key).toEqual('0-0');
-        expect(allowDrop.mock.calls[0][0].dropNode.key).toEqual('0-1-0-0');
-      });
-
-      it('not allow dragging elements outside into tree', () => {
-        const onDrop = jest.fn();
-        const wrapper = mount(
-          <div>
-            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-              <TreeNode key="0-0">
-                <TreeNode key="0-0-0" className="dropTarget" />
-              </TreeNode>
-            </Tree>
-            <div className="dragTarget">Element outside</div>
-          </div>,
-        );
-        wrapper.find('.dragTarget').simulate('dragStart');
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
-        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        expect(onDrop).not.toHaveBeenCalled();
-      });
-    });
-  });
-
-  it('render handler', () => {
-    const wrapper = mount(
-      <Tree
-        draggable={{
-          icon: <span className="handler" />,
-        }}
-        defaultExpandAll
-        treeData={[
-          {
-            title: 'Parent',
-            key: 'parent',
-            children: [
-              {
-                title: 'Child',
-                key: 'child',
-              },
-            ],
-          },
-        ]}
-      />,
-    );
-
-    expect(wrapper.find('.handler')).toHaveLength(2);
-  });
-
-  it('not break with fieldNames', () => {
-    const onDrop = jest.fn();
-
-    const wrapper = mount(
-      <Tree
-        draggable
-        defaultExpandAll
-        onDrop={onDrop}
-        treeData={[
-          {
-            name: 'parent',
-            childList: [
-              {
-                className: 'dragTarget',
-                name: 'child 1',
-              },
-              {
-                className: 'dropTarget',
-                name: 'child 2',
-              },
-            ],
-          },
-        ]}
-        fieldNames={{
-          children: 'childList',
-          title: 'name',
-          key: 'name',
-        }}
-      />,
-    );
-
-    wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
-    wrapper.find('.dropTarget').at(0).simulate('dragEnter', {
-      clientY: -9999999,
-    });
-    wrapper.find('.dropTarget').at(0).simulate('dragOver');
-    wrapper.find('.dropTarget').at(0).simulate('drop');
-
-    expect(onDrop).toHaveBeenCalled();
-  });
-});
diff --git a/tests/TreeDraggable.spec.tsx b/tests/TreeDraggable.spec.tsx
new file mode 100644
index 00000000..27d063da
--- /dev/null
+++ b/tests/TreeDraggable.spec.tsx
@@ -0,0 +1,1118 @@
+/* eslint-disable no-undef, react/no-multi-comp, no-console,
+react/no-unused-state, react/prop-types, no-return-assign */
+import React from 'react';
+import { render, fireEvent, act, createEvent } from '@testing-library/react';
+import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
+import Tree, { TreeNode, FieldDataNode } from '../src';
+import { spyConsole } from './util';
+
+const delay = timeout =>
+  new Promise(resolve => {
+    setTimeout(resolve, timeout);
+  });
+
+describe('Tree Draggable', () => {
+  spyConsole();
+
+  function createTree(props?: any) {
+    return (
+      <Tree draggable defaultExpandAll {...props}>
+        <TreeNode title="parent 1" key="0-0">
+          <TreeNode className="dragTarget" title="leaf" key="0-0-0-0" />
+          <TreeNode className="dropTarget" title="leaf" key="0-0-0-1" />
+        </TreeNode>
+      </Tree>
+    );
+  }
+
+  function fireDragEvent(ele: HTMLElement, eventName: string, data: object = {}) {
+    const event = createEvent[eventName](ele);
+    Object.keys(data).forEach(key => {
+      event[key] = data[key];
+    });
+    fireEvent(ele, event);
+  }
+
+  it('fires dragStart event', () => {
+    const onDragStart = jest.fn();
+    const { container } = render(createTree({ onDragStart }));
+    const treeNode = container.querySelector('.dragTarget > .rc-tree-node-content-wrapper');
+    fireEvent.dragStart(treeNode);
+    const event = onDragStart.mock.calls[0][0];
+    expect(event.node).toEqual(
+      expect.objectContaining({
+        key: '0-0-0-0',
+      }),
+    );
+  });
+
+  it('fires dragEnter event', async () => {
+    const onDragEnter = jest.fn();
+    const { container } = render(createTree({ onDragEnter }));
+
+    fireEvent.dragStart(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+
+    // Not trigger self
+    fireEvent.dragEnter(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+
+    await act(async () => {
+      await delay(900);
+    });
+    expect(onDragEnter).not.toHaveBeenCalled();
+
+    fireEvent.dragEnter(container.querySelector('.dropTarget'));
+    expect(onDragEnter).toHaveBeenCalled();
+
+    await act(async () => {
+      await delay(900);
+    });
+    const event = onDragEnter.mock.calls[0][0];
+    expect(event.node.key).toEqual('0-0-0-1');
+    expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
+    expect(onDragEnter).toHaveBeenCalledTimes(1);
+  });
+
+  it('fires dragOver event', () => {
+    const onDragOver = jest.fn();
+    const { container } = render(createTree({ onDragOver }));
+    fireEvent.dragStart(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+    fireEvent.dragOver(container.querySelector('.dropTarget'));
+    const event = onDragOver.mock.calls[0][0];
+    expect(event.node).toEqual(
+      expect.objectContaining({
+        key: '0-0-0-1',
+      }),
+    );
+  });
+
+  it('fires dragLeave event', () => {
+    const onDragLeave = jest.fn();
+    const { container } = render(createTree({ onDragLeave }));
+    fireEvent.dragLeave(container.querySelector('.dropTarget'));
+    const event = onDragLeave.mock.calls[0][0];
+    expect(event.node).toEqual(
+      expect.objectContaining({
+        key: '0-0-0-1',
+      }),
+    );
+  });
+
+  it('fires drop event', () => {
+    const onDrop = jest.fn();
+    const { container } = render(createTree({ onDrop }));
+    fireEvent.dragStart(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+    fireEvent.dragEnter(container.querySelector('.dropTarget'));
+    fireEvent.dragOver(container.querySelector('.dropTarget'));
+    fireEvent.drop(container.querySelector('.dropTarget'));
+    const event = onDrop.mock.calls[0][0];
+    expect(event.node).toEqual(expect.objectContaining({ key: '0-0-0-1' }));
+    expect(event.dragNode).toEqual(expect.objectContaining({ key: '0-0-0-0' }));
+    expect(event.dragNodesKeys).toEqual(['0-0-0-0']);
+  });
+
+  it('fires dropEnd event', () => {
+    const onDragEnd = jest.fn();
+    const { container } = render(createTree({ onDragEnd }));
+    fireEvent.dragEnd(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+    const event = onDragEnd.mock.calls[0][0];
+    expect(event.node).toEqual(
+      expect.objectContaining({
+        key: '0-0-0-0',
+      }),
+    );
+  });
+
+  it('do not throw error when drag into another non-drag-able tree', () => {
+    const { container } = render(
+      <>
+        <div className="tree1">{createTree()}</div>
+        <div className="tree2">{createTree({ draggable: false })}</div>
+      </>,
+    );
+
+    const dragTree = container.querySelector('.tree1');
+    const normalTree = container.querySelector('.tree2');
+
+    fireEvent.dragStart(dragTree.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+    fireEvent.dragEnter(normalTree.querySelector('.dropTarget'));
+    fireEvent.dragOver(normalTree.querySelector('.dropTarget'));
+    fireEvent.drop(normalTree.querySelector('.dropTarget'));
+  });
+
+  describe('full steps', () => {
+    function dropTarget(targetSelector: string) {
+      return new Promise<void>(resolve => {
+        const { container } = render(
+          <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
+            <TreeNode key="0-0" className="dragTarget">
+              <TreeNode key="0-0-0" className="dragTargetChild" />
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0" />
+            </TreeNode>
+            <TreeNode key="0-2" className="dropTarget">
+              <TreeNode key="0-2-0" />
+            </TreeNode>
+          </Tree>,
+        );
+
+        fireEvent.dragStart(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+
+        // 1. Move into target (first in the middle of the node)
+        fireEvent.dragEnter(container.querySelector(targetSelector), { clientY: 10 });
+        setTimeout(() => {
+          fireEvent.dragOver(container.querySelector(targetSelector), { clientY: 999 });
+
+          // 2. Move out of target
+          fireEvent.dragLeave(container.querySelector(targetSelector));
+
+          // 3. Move in again
+          fireEvent.dragEnter(container.querySelector(targetSelector), { clientY: 0 });
+
+          setTimeout(() => {
+            fireEvent.dragOver(container.querySelector(targetSelector), { clientY: 999 });
+
+            // 4. Drop
+            fireEvent.drop(container.querySelector(targetSelector));
+            fireEvent.dragEnd(container.querySelector('.dragTarget'));
+
+            resolve();
+          }, 1000);
+        }, 10);
+      });
+    }
+
+    let domSpy;
+    beforeEach(() => {
+      domSpy = spyElementPrototypes(HTMLElement, {
+        offsetWidth: {
+          get() {
+            return 24;
+          },
+        },
+        getBoundingClientRect: jest.fn(() => ({
+          width: 100,
+          height: 20,
+          top: 0,
+          left: 0,
+          bottom: 20,
+          right: 100,
+        })),
+      });
+    });
+
+    afterEach(() => {
+      domSpy.mockRestore();
+    });
+
+    it('self', () => dropTarget('div.dragTarget'));
+
+    it('target', () => dropTarget('div.dropTarget'));
+  });
+
+  describe('new drop logic', () => {
+    let domSpy;
+    beforeEach(() => {
+      domSpy = spyElementPrototypes(HTMLElement, {
+        getBoundingClientRect: () => ({
+          width: 100,
+          height: 20,
+          top: 0,
+          left: 0,
+          bottom: 20,
+          right: 100,
+        }),
+        offsetWidth: {
+          get() {
+            return 24;
+          },
+        },
+      });
+    });
+
+    afterEach(() => {
+      domSpy.mockRestore();
+    });
+
+    (['ltr', 'rtl'] as const).forEach(dir => {
+      const base = dir === 'ltr' ? 1 : -1;
+
+      it('not allow cross level dnd on expanded nodes', () => {
+        const onDrop = jest.fn();
+        const { container } = render(
+          <Tree draggable onDrop={onDrop} direction={dir} expandedKeys={['0-0', '0-1', '0-1-0']}>
+            <TreeNode key="0-0">
+              <TreeNode key="0-0-0" className="dropTarget1">
+                <TreeNode key="0-0-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0" className="dropTarget2">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" className="dragTarget" />
+          </Tree>,
+        );
+
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+
+        fireDragEvent(
+          container.querySelector('.dropTarget1 > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+
+        fireDragEvent(
+          container.querySelector('.dropTarget1 > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+
+        fireEvent.drop(container.querySelector('.dropTarget1 > .rc-tree-node-content-wrapper'));
+
+        // insert after 0-0, since 0-0-0 is not expanded
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+
+        fireDragEvent(
+          container.querySelector('.dropTarget2 > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+
+        fireDragEvent(
+          container.querySelector('.dropTarget2 > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+
+        fireEvent.drop(container.querySelector('.dropTarget2 > .rc-tree-node-content-wrapper'));
+
+        // insert into 0-1-0, since it is expanded, do not allow cross level dnd
+        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0');
+        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(0);
+      });
+
+      it('allowDrop all nodes', () => {
+        const onDrop = jest.fn();
+        const { container } = render(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" />
+          </Tree>,
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
+        // (in ltr) drag from right to left, should be insert after, so drop position is 1
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 500,
+            clientY: 600,
+          },
+        );
+
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 500,
+            clientY: 600,
+          },
+        );
+
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+
+        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 550,
+            clientY: 600,
+          },
+        );
+
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 550,
+            clientY: 600,
+          },
+        );
+
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
+
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 600,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 600,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
+      });
+
+      it('allowDrop no node', () => {
+        const onDrop = jest.fn();
+        const { container } = render(
+          <Tree draggable defaultExpandAll onDrop={onDrop} allowDrop={() => false} direction={dir}>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0" className="dropTargetParent">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" />
+          </Tree>,
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTargetParent > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTargetParent > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(
+          container.querySelector('.dropTargetParent > .rc-tree-node-content-wrapper'),
+        );
+        // not allow any dropPosition except 0 on expanded node
+        expect(onDrop).not.toHaveBeenCalled();
+
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop).not.toHaveBeenCalled();
+
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 500,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 500,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop).not.toHaveBeenCalled();
+
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 550,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 550,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop).not.toHaveBeenCalled();
+
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 600,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 600,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop).not.toHaveBeenCalled();
+      });
+
+      it('drop to top half of first node', () => {
+        const onDrop = jest.fn();
+        const { container } = render(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 400,
+            clientY: 0,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 400,
+            clientY: -1000,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
+      });
+
+      it('can drop on its direct parent', () => {
+        const onDrop = jest.fn();
+        const { container } = render(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dragTargetParent > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dragTargetParent > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireEvent.drop(
+          container.querySelector('.dragTargetParent > .rc-tree-node-content-wrapper'),
+        );
+        expect(onDrop).toHaveBeenCalled();
+      });
+
+      it('cover window dragend & componentWillUnmount', () => {
+        const { container, unmount } = render(
+          <Tree draggable defaultExpandAll direction={dir}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        window.dispatchEvent(new Event('dragend'));
+        unmount();
+      });
+
+      it('dragover first half of non-first child', () => {
+        const onDrop = jest.fn();
+        const { container } = render(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+              <TreeNode key="0-0-1" />
+              <TreeNode key="0-0-2" className="dropTarget" />
+            </TreeNode>
+          </Tree>,
+        );
+        // wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+        //   clientX: base * 500,
+        //   clientY: 500,
+        // });
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        // wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+        //   clientX: base * 500,
+        //   clientY: 1,
+        // });
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 500,
+            clientY: 1,
+          },
+        );
+        // wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+        //   clientX: base * 500,
+        //   clientY: 1,
+        // });
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 500,
+            clientY: 1,
+          },
+        );
+        // wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+      });
+
+      it('dragover self', () => {
+        const onDrop = jest.fn();
+        const { container } = render(
+          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-1" className="dropTarget">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-0" className="dragTargetParent">
+              <TreeNode key="0-0-0" className="dragTarget">
+                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              </TreeNode>
+            </TreeNode>
+          </Tree>,
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 400,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 600,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 600,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 600,
+            clientY: 500,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop).not.toHaveBeenCalled();
+      });
+
+      it('not allowDrop on node which has children', () => {
+        const onDrop = jest.fn();
+        const allowDrop = ({ dropNode, dropPosition }) => {
+          if (!dropNode.children) {
+            if (dropPosition === 0) return false;
+          }
+          return true;
+        };
+        const { container } = render(
+          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-0" className="dragTarget">
+              <TreeNode key="0-0-0" className="dragTargetChild" />
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" />
+          </Tree>,
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
+        // (in ltr) drag from right to left, should be insert after, so drop position is 1
+        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 500,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 500,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 550,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 550,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 600,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 600,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
+      });
+
+      it('allowDrop should pass dragNode and dropNode', () => {
+        const onDrop = jest.fn();
+        const allowDrop = jest.fn();
+        const { container } = render(
+          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
+            <TreeNode key="0-0" className="dragTarget">
+              <TreeNode key="0-0-0" className="dragTargetChild" />
+            </TreeNode>
+            <TreeNode key="0-1">
+              <TreeNode key="0-1-0">
+                <TreeNode key="0-1-0-0" className="dropTarget" />
+              </TreeNode>
+            </TreeNode>
+            <TreeNode key="0-2" />
+          </Tree>,
+        );
+        fireDragEvent(
+          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+          'dragStart',
+          {
+            clientX: base * 500,
+            clientY: 500,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragEnter',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+        fireDragEvent(
+          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          'dragOver',
+          {
+            clientX: base * 400,
+            clientY: 600,
+          },
+        );
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(allowDrop.mock.calls[0][0].dragNode.key).toEqual('0-0');
+        expect(allowDrop.mock.calls[0][0].dropNode.key).toEqual('0-1-0-0');
+      });
+
+      it('not allow dragging elements outside into tree', () => {
+        const onDrop = jest.fn();
+        const { container } = render(
+          <div>
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0">
+                <TreeNode key="0-0-0" className="dropTarget" />
+              </TreeNode>
+            </Tree>
+            <div className="dragTarget">Element outside</div>
+          </div>,
+        );
+        fireEvent.dragStart(container.querySelector('.dragTarget'));
+        fireEvent.dragEnter(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        fireEvent.dragOver(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+        expect(onDrop).not.toHaveBeenCalled();
+      });
+    });
+  });
+
+  it('render handler', () => {
+    const { container } = render(
+      <Tree
+        draggable={{
+          icon: <span className="handler" />,
+        }}
+        defaultExpandAll
+        treeData={[
+          {
+            title: 'Parent',
+            key: 'parent',
+            children: [
+              {
+                title: 'Child',
+                key: 'child',
+              },
+            ],
+          },
+        ]}
+      />,
+    );
+
+    expect(container.querySelectorAll('.handler')).toHaveLength(2);
+  });
+
+  it('not break with fieldNames', () => {
+    const onDrop = jest.fn();
+
+    const { container } = render(
+      <Tree<FieldDataNode<{ name: string }, 'childList'>>
+        draggable
+        defaultExpandAll
+        onDrop={onDrop}
+        treeData={[
+          {
+            name: 'parent',
+            childList: [
+              {
+                className: 'dragTarget',
+                name: 'child 1',
+              },
+              {
+                className: 'dropTarget',
+                name: 'child 2',
+              },
+            ],
+          },
+        ]}
+        fieldNames={{
+          children: 'childList',
+          title: 'name',
+          key: 'name',
+        }}
+      />,
+    );
+
+    fireEvent.dragStart(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+    fireDragEvent(container.querySelector('.dropTarget'), 'dragEnter', {
+      clientY: -9999999,
+    });
+    fireEvent.dragOver(container.querySelector('.dropTarget'));
+    fireEvent.drop(container.querySelector('.dropTarget'));
+
+    expect(onDrop).toHaveBeenCalled();
+  });
+});
diff --git a/tests/TreeMotion.spec.js b/tests/TreeMotion.spec.tsx
similarity index 54%
rename from tests/TreeMotion.spec.js
rename to tests/TreeMotion.spec.tsx
index 61aad107..187bc198 100644
--- a/tests/TreeMotion.spec.js
+++ b/tests/TreeMotion.spec.tsx
@@ -1,17 +1,34 @@
 import React from 'react';
-import { mount } from 'enzyme';
-import { act } from 'react-dom/test-utils';
-import Tree, { TreeNode } from '../src';
+import { render, fireEvent, act } from '@testing-library/react';
+import Tree, { TreeNode, FieldDataNode } from '../src';
 import MotionTreeNode from '../src/MotionTreeNode';
 import { TreeContext } from '../src/contextTypes';
 import { getMinimumRangeTransitionRange } from '../src/NodeList';
 
+jest.mock('rc-motion/lib/util/motion', () => {
+  const origin = jest.requireActual('rc-motion/lib/util/motion');
+
+  return {
+    ...origin,
+    supportTransition: () => true,
+  };
+});
+
 describe('Tree Motion', () => {
+  beforeEach(() => {
+    jest.useFakeTimers();
+  });
+
+  afterEach(() => {
+    jest.clearAllTimers();
+    jest.useRealTimers();
+  });
+
   it('basic', () => {
     const motion = {
       motionName: 'bamboo',
     };
-    const wrapper = mount(
+    const { container } = render(
       <Tree motion={motion} height={10000}>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" />
@@ -19,33 +36,32 @@ describe('Tree Motion', () => {
       </Tree>,
     );
 
-    const switcher = wrapper.find('.rc-tree-switcher');
-    switcher.simulate('click');
+    fireEvent.click(container.querySelector('.rc-tree-switcher'));
 
-    expect(wrapper.find('CSSMotion').props()).toMatchObject(motion);
+    expect(container.querySelector('.bamboo-appear')).toBeTruthy();
   });
 
-  it('hide item', done => {
-    const wrapper = mount(
+  it('hide item', () => {
+    const renderTree = (props?: any) => (
       <Tree
         treeData={[{ key: '0-0', children: [{ key: '0-0-0' }] }]}
         expandedKeys={['0-0']}
         motion={{}}
-      />,
+        {...props}
+      />
     );
-    wrapper.setProps({ expandedKeys: [] });
-
-    setTimeout(() => {
-      wrapper.update();
-      // Confirm CSSMotion is prepare to hide
-      expect(wrapper.find('CSSMotion').props().visible).toBeFalsy();
-      done();
-    }, 100);
+
+    const { container, rerender } = render(renderTree());
+
+    rerender(renderTree({ expandedKeys: [] }));
+
+    expect(container.querySelector('.bamboo-appear')).toBeFalsy();
   });
 
   it('getMinimumRangeTransitionRange', () => {
     const visibleList = getMinimumRangeTransitionRange(
-      new Array(100).fill(null).map((_, index) => index),
+      new Array(100).fill(null).map((_, index) => index) as any,
+      true,
       100,
       20,
     );
@@ -54,25 +70,26 @@ describe('Tree Motion', () => {
   });
 
   it('not crash', () => {
-    const wrapper = mount(
+    const renderTree = (props?: any) => (
       <Tree
         treeData={[{ key: '0-0', children: [{ key: '0-0-0' }] }]}
         expandedKeys={['0-0']}
         motion={{}}
-      />,
+        {...props}
+      />
     );
+    const { rerender } = render(renderTree());
 
-    wrapper.setProps({ treeData: [] });
+    rerender(renderTree({ treeData: [] }));
   });
 
   it('should not expanded when in motion', () => {
-    const raf = jest
-      .spyOn(window, 'requestAnimationFrame')
-      .mockImplementation(fn => window.setTimeout(fn, 16));
-    jest.useFakeTimers();
+    // const raf = jest
+    //   .spyOn(window, 'requestAnimationFrame')
+    //   .mockImplementation(fn => window.setTimeout(fn, 16));
 
     const onExpand = jest.fn();
-    const wrapper = mount(
+    const { container } = render(
       <Tree
         onExpand={onExpand}
         motion={{
@@ -88,8 +105,7 @@ describe('Tree Motion', () => {
     );
 
     function doExpand() {
-      const switcher = wrapper.find('.rc-tree-switcher').first();
-      switcher.simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
     }
 
     // First click should work
@@ -100,21 +116,19 @@ describe('Tree Motion', () => {
     // Not trigger when in motion
     doExpand();
     expect(onExpand).not.toHaveBeenCalled();
-
-    raf.mockRestore();
-    jest.useRealTimers();
   });
 
   describe('MotionTreeNode should always trigger motion end', () => {
     it('with motionNodes', () => {
       const onMotionStart = jest.fn();
       const onMotionEnd = jest.fn();
-      const wrapper = mount(
-        <TreeContext.Provider value={{ prefixCls: 'test' }}>
+      const { unmount } = render(
+        <TreeContext.Provider value={{ prefixCls: 'test' } as any}>
           <MotionTreeNode
             motionNodes={[]}
             onMotionStart={onMotionStart}
             onMotionEnd={onMotionEnd}
+            {...({} as any)} // Ignore TS warning
           />
         </TreeContext.Provider>,
       );
@@ -122,29 +136,36 @@ describe('Tree Motion', () => {
       expect(onMotionStart).toHaveBeenCalled();
       expect(onMotionEnd).not.toHaveBeenCalled();
 
-      wrapper.unmount();
+      unmount();
       expect(onMotionEnd).toHaveBeenCalled();
     });
 
     it('without motionNodes', () => {
       const onMotionStart = jest.fn();
       const onMotionEnd = jest.fn();
-      const wrapper = mount(
+      const { unmount } = render(
         <TreeContext.Provider
-          value={{
-            prefixCls: 'test',
-            keyEntities: {},
-            dropIndicatorRender: () => null,
-          }}
+          value={
+            {
+              prefixCls: 'test',
+              keyEntities: {},
+              dropIndicatorRender: () => null,
+            } as any
+          }
         >
-          <MotionTreeNode onMotionStart={onMotionStart} onMotionEnd={onMotionEnd} isEnd={[false]} />
+          <MotionTreeNode
+            onMotionStart={onMotionStart}
+            onMotionEnd={onMotionEnd}
+            isEnd={[false]}
+            {...({} as any)} // Ignore TS warning
+          />
         </TreeContext.Provider>,
       );
 
       expect(onMotionStart).not.toHaveBeenCalled();
       expect(onMotionEnd).not.toHaveBeenCalled();
 
-      wrapper.unmount();
+      unmount();
       expect(onMotionStart).not.toHaveBeenCalled();
       expect(onMotionEnd).not.toHaveBeenCalled();
     });
@@ -152,7 +173,7 @@ describe('Tree Motion', () => {
 
   it('motion should work well with fieldNames', () => {
     const Demo = () => (
-      <Tree
+      <Tree<FieldDataNode<{ id: string; name: string }, 'sub'>>
         defaultExpandAll
         fieldNames={{
           title: 'name',
@@ -178,16 +199,19 @@ describe('Tree Motion', () => {
       />
     );
 
-    const wrapper = mount(<Demo />);
-    expect(wrapper.find('[title="B"]').length).toBeTruthy();
-    wrapper.find('.rc-tree-switcher').first().simulate('click');
-    expect(wrapper.find('[title="B"]').length).toBe(0);
+    const { container } = render(<Demo />);
+    expect(container.querySelector('[title="B"]')).toBeTruthy();
+    // wrapper.find('.rc-tree-switcher').first().simulate('click');
+    fireEvent.click(container.querySelector('.rc-tree-switcher'));
+    act(() => {
+      jest.runAllTimers();
+    });
+    fireEvent.animationEnd(container.querySelector('.bamboo-leave-active'));
+    expect(container.querySelector('[title="B"]')).toBeFalsy();
   });
 
   it('motion should not revert flatten list', () => {
-    jest.useFakeTimers();
-
-    const wrapper = mount(
+    const renderTree = (props?: any) => (
       <Tree
         motion={{
           motionName: 'little',
@@ -206,12 +230,17 @@ describe('Tree Motion', () => {
             ],
           },
         ]}
-      />,
+        {...props}
+      />
     );
 
-    wrapper.setProps({
-      expandedKeys: ['parent'],
-    });
+    const { container, rerender } = render(renderTree());
+
+    rerender(
+      renderTree({
+        expandedKeys: ['parent'],
+      }),
+    );
 
     for (let i = 0; i < 10; i += 1) {
       act(() => {
@@ -219,26 +248,24 @@ describe('Tree Motion', () => {
       });
     }
 
-    wrapper.setProps({
-      treeData: [
-        {
-          key: 'parent',
-          title: 'parent2',
-          children: [
-            {
-              key: 'child',
-              title: 'child2',
-            },
-          ],
-        },
-      ],
-    });
-
-    wrapper.find('.rc-tree-treenode-motion').simulate('animationEnd');
-
-    expect(wrapper.find('span.rc-tree-title').at(0).text()).toEqual('parent2');
-    expect(wrapper.find('span.rc-tree-title').at(1).text()).toEqual('child2');
+    rerender(
+      renderTree({
+        treeData: [
+          {
+            key: 'parent',
+            title: 'parent2',
+            children: [
+              {
+                key: 'child',
+                title: 'child2',
+              },
+            ],
+          },
+        ],
+      }),
+    );
 
-    jest.useRealTimers();
+    expect(container.querySelectorAll('span.rc-tree-title')[0].textContent).toEqual('parent2');
+    expect(container.querySelectorAll('span.rc-tree-title')[1].textContent).toEqual('child2');
   });
 });
diff --git a/tests/TreeNodeProps.spec.js b/tests/TreeNodeProps.spec.tsx
similarity index 76%
rename from tests/TreeNodeProps.spec.js
rename to tests/TreeNodeProps.spec.tsx
index 681025ff..b51a6954 100644
--- a/tests/TreeNodeProps.spec.js
+++ b/tests/TreeNodeProps.spec.tsx
@@ -1,7 +1,6 @@
 /* eslint-disable no-undef */
 import React from 'react';
-import { render, mount } from 'enzyme';
-import { renderToJson } from 'enzyme-to-json';
+import { render, fireEvent } from '@testing-library/react';
 import Tree, { TreeNode } from '../src';
 import { spyConsole } from './util';
 
@@ -16,7 +15,7 @@ describe('TreeNode Props', () => {
   // expanded - is defined by Tree, TreeNode can not change it
 
   it('className', () => {
-    const wrapper = render(
+    const { container } = render(
       <Tree defaultExpandAll>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" className="tree-node-cls">
@@ -27,7 +26,7 @@ describe('TreeNode Props', () => {
         <TreeNode key="0-1" />
       </Tree>,
     );
-    expect(renderToJson(wrapper)).toMatchSnapshot();
+    expect(container.firstChild).toMatchSnapshot();
   });
 
   // disabled - is already full test in Tree.spec.js
@@ -42,7 +41,7 @@ describe('TreeNode Props', () => {
         </TreeNode>
       </Tree>,
     );
-    expect(renderToJson(withoutLoadData)).toMatchSnapshot();
+    expect(withoutLoadData.container.firstChild).toMatchSnapshot();
 
     const withLoadData = render(
       <Tree defaultExpandAll>
@@ -51,7 +50,7 @@ describe('TreeNode Props', () => {
         </TreeNode>
       </Tree>,
     );
-    expect(renderToJson(withLoadData)).toMatchSnapshot();
+    expect(withLoadData.container.firstChild).toMatchSnapshot();
 
     const forceNoLeaf = render(
       <Tree defaultExpandAll>
@@ -60,16 +59,16 @@ describe('TreeNode Props', () => {
         </TreeNode>
       </Tree>,
     );
-    expect(renderToJson(forceNoLeaf)).toMatchSnapshot();
+    expect(forceNoLeaf.container.firstChild).toMatchSnapshot();
   });
 
   it('title function', () => {
-    const wrapper = render(
+    const { container } = render(
       <Tree defaultExpandAll>
         <TreeNode title={() => <a id="test-title">title</a>} />
       </Tree>,
     );
-    expect(wrapper.find('#test-title').text()).toEqual('title');
+    expect(container.querySelector('#test-title').textContent).toEqual('title');
   });
 
   describe('customize icon', () => {
@@ -79,7 +78,7 @@ describe('TreeNode Props', () => {
           <TreeNode icon={<span className="cust-icon" />} />
         </Tree>,
       );
-      expect(renderToJson(withoutLoadData)).toMatchSnapshot();
+      expect(withoutLoadData.container.firstChild).toMatchSnapshot();
     });
 
     it('component', () => {
@@ -90,7 +89,7 @@ describe('TreeNode Props', () => {
           <TreeNode icon={Icon} />
         </Tree>,
       );
-      expect(renderToJson(withoutLoadData)).toMatchSnapshot();
+      expect(withoutLoadData.container.firstChild).toMatchSnapshot();
     });
 
     it('hide icon', () => {
@@ -99,23 +98,23 @@ describe('TreeNode Props', () => {
           <TreeNode icon={<span className="cust-icon" />} />
         </Tree>,
       );
-      expect(renderToJson(withoutLoadData)).toMatchSnapshot();
+      expect(withoutLoadData.container.firstChild).toMatchSnapshot();
     });
 
     it('get props when loading', () => {
       const then = jest.fn(() => Promise.resolve());
-      const loadData = jest.fn(() => ({ then }));
-      const iconFn = jest.fn(() => null);
-      const wrapper = mount(
+      const loadData: any = jest.fn(() => ({ then }));
+      const iconFn: any = jest.fn(() => null);
+      const { container } = render(
         <Tree loadData={loadData}>
           <TreeNode icon={iconFn} title="parent 1" key="0-0" />
         </Tree>,
       );
 
-      wrapper.find('.rc-tree-switcher').simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
 
       expect(iconFn.mock.calls[0][0].loading).toBe(false);
-      expect(iconFn.mock.calls[iconFn.mock.calls.length - 1][0].loading).toBe(true);
+      expect(iconFn.mock.calls[iconFn.mock.calls.length - 1][0].loading).toBeTruthy();
     });
   });
 
@@ -132,7 +131,7 @@ describe('TreeNode Props', () => {
           <TreeNode key="0-1" data-test="0-1" />
         </Tree>,
       );
-      expect(renderToJson(wrapper)).toMatchSnapshot();
+      expect(wrapper.container.firstChild).toMatchSnapshot();
     });
 
     it('renders aria attributes on li', () => {
@@ -147,7 +146,7 @@ describe('TreeNode Props', () => {
           <TreeNode key="0-1" aria-label="0-1" />
         </Tree>,
       );
-      expect(renderToJson(wrapper)).toMatchSnapshot();
+      expect(wrapper.container.firstChild).toMatchSnapshot();
     });
   });
 
@@ -155,13 +154,13 @@ describe('TreeNode Props', () => {
     const onClick = jest.fn();
     const onSelect = jest.fn();
 
-    const wrapper = mount(
+    const { container } = render(
       <Tree selectable onClick={onClick} onSelect={onSelect}>
         <TreeNode selectable={false} />
       </Tree>,
     );
 
-    wrapper.find('.rc-tree-node-content-wrapper').simulate('click');
+    fireEvent.click(container.querySelector('.rc-tree-node-content-wrapper'));
 
     expect(onClick).toHaveBeenCalled();
     expect(onSelect).not.toHaveBeenCalled();
@@ -198,19 +197,20 @@ describe('TreeNode Props', () => {
       }
     }
 
-    const wrapper = mount(<Demo />);
+    const { container } = render(<Demo />);
     // tree selectable is false ,then children should be selectable = false if not set selectable alone.
-    expect(wrapper.find('[aria-selected=false]').length).toBe(1);
-    wrapper.find('.rc-tree-node-content-wrapper').at(1).simulate('click');
+    expect(container.querySelectorAll('[aria-selected=false]')).toHaveLength(1);
+
+    fireEvent.click(container.querySelector('.rc-tree-node-content-wrapper'));
     expect(onClick).toHaveBeenCalled();
     expect(onSelect).not.toHaveBeenCalled();
 
     // only set tree node use state.
-    wrapper.find('.test-button').simulate('click');
+    fireEvent.click(container.querySelector('.test-button'));
     onClick.mockRestore();
     onSelect.mockRestore();
-    expect(wrapper.find('[aria-selected=false]').length).toBe(1);
-    wrapper.find('.rc-tree-node-content-wrapper').at(1).simulate('click');
+    expect(container.querySelectorAll('[aria-selected=false]')).toHaveLength(1);
+    fireEvent.click(container.querySelectorAll('.rc-tree-node-content-wrapper')[1]);
     expect(onClick).toHaveBeenCalled();
     expect(onSelect).not.toHaveBeenCalled();
   });
diff --git a/tests/TreeProps.spec.js b/tests/TreeProps.spec.tsx
similarity index 69%
rename from tests/TreeProps.spec.js
rename to tests/TreeProps.spec.tsx
index 7d9a4670..7578c268 100644
--- a/tests/TreeProps.spec.js
+++ b/tests/TreeProps.spec.tsx
@@ -1,12 +1,9 @@
 /* eslint-disable no-undef, react/no-multi-comp */
 import React from 'react';
-import { render, mount } from 'enzyme';
-import { renderToJson } from 'enzyme-to-json';
+import { render, fireEvent, act } from '@testing-library/react';
 import { resetWarned } from 'rc-util/lib/warning';
-import Tree, { TreeNode } from '../src';
-import { InternalTreeNode } from '../src/TreeNode';
+import Tree, { TreeNode, FieldDataNode } from '../src';
 import { objectMatcher, spyConsole, spyError } from './util';
-import { convertNodePropsToEventData } from '../src/utils/treeUtil';
 
 /**
  * For refactor purpose. All the props should be passed by test
@@ -25,16 +22,16 @@ describe('Tree Props', () => {
   // prefixCls
   it('prefixCls', () => {
     const withoutPrefix = render(<Tree />);
-    expect(renderToJson(withoutPrefix)).toMatchSnapshot();
+    expect(withoutPrefix.container.firstChild).toMatchSnapshot();
 
     const withPrefix = render(<Tree prefixCls="test-prefix" />);
-    expect(renderToJson(withPrefix)).toMatchSnapshot();
+    expect(withPrefix.container.firstChild).toMatchSnapshot();
   });
 
   // showLine
   it('showLine', () => {
     const wrapper = render(<Tree showLine />);
-    expect(renderToJson(wrapper)).toMatchSnapshot();
+    expect(wrapper.container.firstChild).toMatchSnapshot();
   });
 
   // showIcon
@@ -50,7 +47,7 @@ describe('Tree Props', () => {
         <TreeNode />
       </Tree>,
     );
-    expect(renderToJson(withIcon)).toMatchSnapshot();
+    expect(withIcon.container.firstChild).toMatchSnapshot();
 
     const withoutIcon = render(
       <Tree showIcon={false}>
@@ -63,7 +60,7 @@ describe('Tree Props', () => {
         <TreeNode />
       </Tree>,
     );
-    expect(renderToJson(withoutIcon)).toMatchSnapshot();
+    expect(withoutIcon.container.firstChild).toMatchSnapshot();
 
     const withOpenIcon = render(
       <Tree defaultExpandedKeys={['0-0']}>
@@ -76,7 +73,7 @@ describe('Tree Props', () => {
         <TreeNode />
       </Tree>,
     );
-    expect(renderToJson(withOpenIcon)).toMatchSnapshot();
+    expect(withOpenIcon.container.firstChild).toMatchSnapshot();
   });
 
   describe('selectable', () => {
@@ -85,21 +82,17 @@ describe('Tree Props', () => {
       const handleOnSelect = jest.fn();
       const handleOnCheck = jest.fn();
 
-      const withoutSelectableBase = (
+      const { container } = render(
         <Tree onSelect={handleOnSelect} defaultExpandedKeys={['0-0']} selectable={false}>
           <TreeNode key="0-0">
             <TreeNode key="0-0-0" />
           </TreeNode>
-        </Tree>
+        </Tree>,
       );
 
-      expect(renderToJson(render(withoutSelectableBase))).toMatchSnapshot();
-
-      const withoutSelectable = mount(withoutSelectableBase);
-      const parentNode = withoutSelectable.find(InternalTreeNode).first();
-      const targetNode = parentNode.find(InternalTreeNode).last();
+      expect(container.firstChild).toMatchSnapshot();
 
-      targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-node-content-wrapper'));
 
       expect(handleOnSelect).not.toHaveBeenCalled();
       expect(handleOnCheck).not.toHaveBeenCalled(); // Will test in checkable
@@ -109,23 +102,23 @@ describe('Tree Props', () => {
     it('with selectable', () => {
       const handleOnSelect = jest.fn();
 
-      const withSelectableBase = (
+      const { container } = render(
         <Tree onSelect={handleOnSelect} defaultExpandedKeys={['0-0']}>
           <TreeNode key="0-0">
             <TreeNode key="0-0-0" />
           </TreeNode>
-        </Tree>
+        </Tree>,
       );
 
-      expect(renderToJson(render(withSelectableBase))).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
 
-      const withSelectable = mount(withSelectableBase);
-      const getTargetNode = () => withSelectable.find(InternalTreeNode).last();
-      const getParentNode = () => withSelectable.find(InternalTreeNode).first();
+      const getNodes = () =>
+        container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode');
+      const getTargetNode = () => getNodes()[getNodes().length - 1];
+      const getParentNode = () => getNodes()[0];
 
       // Select leaf
-      let node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
+      fireEvent.click(getTargetNode().querySelector('.rc-tree-node-content-wrapper'));
 
       // traverseTreeNodes loops origin TreeNode and
       // onSelect trigger on `cloneElement` which is not the same instance
@@ -134,7 +127,7 @@ describe('Tree Props', () => {
         objectMatcher({
           event: 'select',
           selected: true,
-          node,
+          node: { key: '0-0-0' },
           selectedNodes: [{ key: '0-0-0' }],
           nativeEvent: {},
         }),
@@ -142,14 +135,13 @@ describe('Tree Props', () => {
       handleOnSelect.mockReset();
 
       // un-select leaf
-      node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
+      fireEvent.click(getTargetNode().querySelector('.rc-tree-node-content-wrapper'));
       expect(handleOnSelect).toHaveBeenCalledWith(
         [],
         objectMatcher({
           event: 'select',
           selected: false,
-          node,
+          node: { key: '0-0-0' },
           selectedNodes: [],
           nativeEvent: {},
         }),
@@ -157,28 +149,26 @@ describe('Tree Props', () => {
       handleOnSelect.mockReset();
 
       // Select leaf and then parent
-      node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
+      fireEvent.click(getTargetNode().querySelector('.rc-tree-node-content-wrapper'));
       expect(handleOnSelect).toHaveBeenCalledWith(
         ['0-0-0'],
         objectMatcher({
           event: 'select',
           selected: true,
-          node,
+          node: { key: '0-0-0' },
           selectedNodes: [{ key: '0-0-0' }],
           nativeEvent: {},
         }),
       );
       handleOnSelect.mockReset();
 
-      node = convertNodePropsToEventData(getParentNode().props());
-      getParentNode().find('.rc-tree-node-content-wrapper').first().simulate('click');
+      fireEvent.click(getParentNode().querySelector('.rc-tree-node-content-wrapper'));
       expect(handleOnSelect).toHaveBeenCalledWith(
         ['0-0'],
         objectMatcher({
           event: 'select',
           selected: true,
-          node,
+          node: { key: '0-0' },
           selectedNodes: [{ key: '0-0' }],
           nativeEvent: {},
         }),
@@ -192,7 +182,7 @@ describe('Tree Props', () => {
       const onSelect = jest.fn();
       const onExpand = jest.fn();
 
-      const wrapper = mount(
+      const { container } = render(
         <Tree
           onClick={onClick}
           onSelect={onSelect}
@@ -214,7 +204,7 @@ describe('Tree Props', () => {
       );
 
       // test trigger expand when click title
-      wrapper.find('[title="leaf 1"]').hostNodes().simulate('click');
+      fireEvent.click(container.querySelector('[title="leaf 1"]'));
 
       expect(onClick).toHaveBeenCalled();
       expect(onSelect).not.toHaveBeenCalled();
@@ -229,7 +219,7 @@ describe('Tree Props', () => {
       onExpand.mockReset();
 
       // test trigger un-expand when click title again
-      wrapper.find('[title="leaf 1"]').hostNodes().simulate('click');
+      fireEvent.click(container.querySelector('[title="leaf 1"]'));
 
       expect(onClick).toHaveBeenCalled();
       expect(onSelect).not.toHaveBeenCalled();
@@ -245,7 +235,7 @@ describe('Tree Props', () => {
       const onSelect = jest.fn();
       const onExpand = jest.fn();
 
-      const wrapper = mount(
+      const { container } = render(
         <Tree
           onDoubleClick={onDoubleClick}
           onSelect={onSelect}
@@ -267,7 +257,7 @@ describe('Tree Props', () => {
       );
 
       // test trigger expand when double click title
-      wrapper.find('[title="leaf 1"]').hostNodes().simulate('doubleclick');
+      fireEvent.doubleClick(container.querySelector('[title="leaf 1"]'));
 
       expect(onDoubleClick).toHaveBeenCalled();
       expect(onSelect).not.toHaveBeenCalled();
@@ -282,7 +272,7 @@ describe('Tree Props', () => {
       onExpand.mockReset();
 
       // test trigger un-expand when double click title again
-      wrapper.find('[title="leaf 1"]').hostNodes().simulate('doubleclick');
+      fireEvent.doubleClick(container.querySelector('[title="leaf 1"]'));
 
       expect(onDoubleClick).toHaveBeenCalled();
       expect(onSelect).not.toHaveBeenCalled();
@@ -298,7 +288,7 @@ describe('Tree Props', () => {
       const onSelect = jest.fn();
       const onExpand = jest.fn();
 
-      const wrapper = mount(
+      const { container } = render(
         <Tree
           onClick={onClick}
           onSelect={onSelect}
@@ -320,7 +310,7 @@ describe('Tree Props', () => {
       );
 
       // test won't trigger expand when click title if expandAction is false
-      wrapper.find('[title="leaf 2"]').hostNodes().simulate('click');
+      fireEvent.click(container.querySelector('[title="leaf 2"]'));
 
       expect(onClick).toHaveBeenCalled();
       expect(onSelect).not.toHaveBeenCalled();
@@ -332,29 +322,29 @@ describe('Tree Props', () => {
   it('multiple', () => {
     const handleOnSelect = jest.fn();
 
-    const multipleBase = (
+    const { container } = render(
       <Tree onSelect={handleOnSelect} defaultExpandAll multiple>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" />
         </TreeNode>
-      </Tree>
+      </Tree>,
     );
 
-    expect(renderToJson(render(multipleBase))).toMatchSnapshot();
+    expect(container.firstChild).toMatchSnapshot();
 
-    const wrapper = mount(multipleBase);
-    const getParentNode = () => wrapper.find(InternalTreeNode).first();
-    const getTargetNode = () => wrapper.find(InternalTreeNode).last();
+    const getNodes = () =>
+      container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode');
+    const getTargetNode = () => getNodes()[getNodes().length - 1];
+    const getParentNode = () => getNodes()[0];
 
     // Leaf select
-    let node = convertNodePropsToEventData(getTargetNode().props());
-    getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
+    fireEvent.click(getTargetNode().querySelector('.rc-tree-node-content-wrapper'));
     expect(handleOnSelect).toHaveBeenCalledWith(
       ['0-0-0'],
       objectMatcher({
         event: 'select',
         selected: true,
-        node,
+        node: { key: '0-0-0' },
         selectedNodes: [{ key: '0-0-0' }],
         nativeEvent: {},
       }),
@@ -362,14 +352,13 @@ describe('Tree Props', () => {
     handleOnSelect.mockReset();
 
     // Parent select
-    node = convertNodePropsToEventData(getParentNode().props());
-    getParentNode().find('.rc-tree-node-content-wrapper').first().simulate('click');
+    fireEvent.click(getParentNode().querySelector('.rc-tree-node-content-wrapper'));
     expect(handleOnSelect).toHaveBeenCalledWith(
       ['0-0-0', '0-0'],
       objectMatcher({
         event: 'select',
         selected: true,
-        node,
+        node: { key: '0-0' },
         selectedNodes: [{ key: '0-0-0' }, { key: '0-0' }],
         nativeEvent: {},
       }),
@@ -377,14 +366,13 @@ describe('Tree Props', () => {
     handleOnSelect.mockReset();
 
     // Leaf un-select
-    node = convertNodePropsToEventData(getTargetNode().props());
-    getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
+    fireEvent.click(getTargetNode().querySelector('.rc-tree-node-content-wrapper'));
     expect(handleOnSelect).toHaveBeenCalledWith(
       ['0-0'],
       objectMatcher({
         event: 'select',
         selected: false,
-        node,
+        node: { key: '0-0-0' },
         selectedNodes: [{ key: '0-0' }],
         nativeEvent: {},
       }),
@@ -397,7 +385,7 @@ describe('Tree Props', () => {
       const handleOnSelect = jest.fn();
       const handleOnCheck = jest.fn();
 
-      const withCheckableBase = (
+      const { container } = render(
         <Tree
           onSelect={handleOnSelect}
           onCheck={handleOnCheck}
@@ -407,23 +395,23 @@ describe('Tree Props', () => {
           <TreeNode key="0-0">
             <TreeNode key="0-0-0" />
           </TreeNode>
-        </Tree>
+        </Tree>,
       );
 
-      expect(renderToJson(render(withCheckableBase))).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
 
-      const withCheckable = mount(withCheckableBase);
-      const getTargetNode = () => withCheckable.find(InternalTreeNode).last();
+      const getNodes = () =>
+        container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode');
+      const getTargetNode = () => getNodes()[getNodes().length - 1];
 
       // Click leaf
-      let node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
+      fireEvent.click(getTargetNode().querySelector('.rc-tree-node-content-wrapper'));
       expect(handleOnSelect).toHaveBeenCalledWith(
         ['0-0-0'],
         objectMatcher({
           event: 'select',
           selected: true,
-          node,
+          node: { key: '0-0-0' },
           selectedNodes: [{ key: '0-0-0' }],
           nativeEvent: {},
         }),
@@ -435,15 +423,14 @@ describe('Tree Props', () => {
       handleOnSelect.mockReset();
 
       // Click checkbox
-      node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode().find('.rc-tree-checkbox').simulate('click');
+      fireEvent.click(getTargetNode().querySelector('.rc-tree-checkbox'));
 
       expect(handleOnCheck).toHaveBeenCalledWith(
         ['0-0-0', '0-0'],
         objectMatcher({
           event: 'check',
           checked: true,
-          node,
+          node: { key: '0-0-0' },
           checkedNodes: [{ key: '0-0-0' }, { key: '0-0' }],
           nativeEvent: {},
         }),
@@ -455,7 +442,7 @@ describe('Tree Props', () => {
       const handleOnSelect = jest.fn();
       const handleOnCheck = jest.fn();
 
-      const withCheckableBase = (
+      const { container } = render(
         <Tree
           onSelect={handleOnSelect}
           onCheck={handleOnCheck}
@@ -466,23 +453,23 @@ describe('Tree Props', () => {
           <TreeNode key="0-0">
             <TreeNode key="0-0-0" />
           </TreeNode>
-        </Tree>
+        </Tree>,
       );
 
-      expect(renderToJson(render(withCheckableBase))).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
 
-      const withCheckable = mount(withCheckableBase);
-      const getTargetNode = () => withCheckable.find(InternalTreeNode).last();
+      const getNodes = () =>
+        container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode');
+      const getTargetNode = () => getNodes()[getNodes().length - 1];
 
       // Click leaf
-      const node = convertNodePropsToEventData(getTargetNode().props());
-      getTargetNode().find('.rc-tree-node-content-wrapper').simulate('click');
+      fireEvent.click(getTargetNode().querySelector('.rc-tree-node-content-wrapper'));
       expect(handleOnCheck).toHaveBeenCalledWith(
         ['0-0-0', '0-0'],
         objectMatcher({
           event: 'check',
           checked: true,
-          node,
+          node: { key: '0-0-0' },
           checkedNodes: [{ key: '0-0-0' }, { key: '0-0' }],
           nativeEvent: {},
         }),
@@ -491,7 +478,7 @@ describe('Tree Props', () => {
     });
 
     it('node set checkable to `false`', () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree checkable defaultExpandAll>
           <TreeNode key="0-0">
             <TreeNode key="0-0-0" checkable={false} />
@@ -499,17 +486,16 @@ describe('Tree Props', () => {
         </Tree>,
       );
 
-      expect(wrapper.find('TreeNode').at(0).find('.rc-tree-checkbox').length).toBeTruthy();
-      expect(wrapper.find('TreeNode').at(1).find('.rc-tree-checkbox').length).toBeFalsy();
+      expect(container.querySelectorAll('.rc-tree-checkbox')).toHaveLength(1);
     });
   });
 
-  // // Don't crash
+  // Don't crash
   describe('invalidate checkedKeys', () => {
     const errorSpy = spyError();
 
     const genWrapper = checkedKeys =>
-      mount(
+      render(
         <Tree checkedKeys={checkedKeys} defaultExpandAll checkable>
           <TreeNode key="0-0">
             <TreeNode key="0-0-0" />
@@ -518,19 +504,19 @@ describe('Tree Props', () => {
       );
 
     it('null', () => {
-      const wrapper = genWrapper(null);
+      const { container } = genWrapper(null);
       expect(errorSpy()).not.toHaveBeenCalledWith(
         'Warning: `checkedKeys` is not an array or an object',
       );
-      expect(wrapper.render()).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
     });
 
     it('number', () => {
-      const wrapper = genWrapper(123);
+      const { container } = genWrapper(123);
       expect(errorSpy()).toHaveBeenCalledWith(
         'Warning: `checkedKeys` is not an array or an object',
       );
-      expect(wrapper.render()).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
     });
   });
 
@@ -538,7 +524,7 @@ describe('Tree Props', () => {
   it('checkStrictly', () => {
     const handleOnCheck = jest.fn();
 
-    const wrapper = mount(
+    const { container } = render(
       <Tree onCheck={handleOnCheck} defaultExpandedKeys={['0-0']} checkable checkStrictly>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" />
@@ -546,13 +532,14 @@ describe('Tree Props', () => {
       </Tree>,
     );
 
-    expect(wrapper.render()).toMatchSnapshot();
+    expect(container.firstChild).toMatchSnapshot();
 
-    const getTargetNode = () => wrapper.find(InternalTreeNode).last();
+    const getNodes = () =>
+      container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode');
+    const getTargetNode = () => getNodes()[getNodes().length - 1];
 
     // Click Leaf
-    const node = convertNodePropsToEventData(getTargetNode().props());
-    getTargetNode().find('.rc-tree-checkbox').simulate('click');
+    fireEvent.click(getTargetNode().querySelector('.rc-tree-checkbox'));
     expect(handleOnCheck).toHaveBeenCalledWith(
       {
         checked: ['0-0-0'],
@@ -561,7 +548,9 @@ describe('Tree Props', () => {
       objectMatcher({
         event: 'check',
         checked: true,
-        node,
+        node: {
+          key: '0-0-0',
+        },
         checkedNodes: [{ key: '0-0-0' }],
         nativeEvent: {},
       }),
@@ -573,7 +562,7 @@ describe('Tree Props', () => {
 
   // defaultExpandAll
   it('defaultExpandAll', () => {
-    const wrapper = render(
+    const { container } = render(
       <Tree defaultExpandAll>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" />
@@ -581,7 +570,7 @@ describe('Tree Props', () => {
       </Tree>,
     );
 
-    expect(renderToJson(wrapper)).toMatchSnapshot();
+    expect(container.firstChild).toMatchSnapshot();
   });
 
   // defaultCheckedKeys - is already full test in Tree.spec.js
@@ -590,7 +579,7 @@ describe('Tree Props', () => {
   describe('loadData', () => {
     const errorSpy = spyError();
 
-    it('basic', () => {
+    it('basic', async () => {
       let called = 0;
 
       const handleLoadData = jest.fn();
@@ -600,7 +589,7 @@ describe('Tree Props', () => {
           loaded: false,
         };
 
-        loadData = (...args) => {
+        loadData = (...args: any[]) => {
           called += 1;
           handleLoadData(...args);
 
@@ -619,23 +608,25 @@ describe('Tree Props', () => {
         }
       }
 
-      const wrapper = mount(<Demo />);
+      const { container } = render(<Demo />);
 
       expect(handleLoadData).not.toHaveBeenCalled();
 
-      const switcher = wrapper.find('.rc-tree-switcher');
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
-      switcher.simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
 
-      return timeoutPromise().then(() => {
-        expect(handleLoadData).toHaveBeenCalledWith(node);
-        expect(called).toBe(1);
-        expect(renderToJson(wrapper.render())).toMatchSnapshot();
-      });
+      await timeoutPromise(100);
+
+      expect(handleLoadData).toHaveBeenCalledWith(
+        expect.objectContaining({
+          key: '0-0',
+        }),
+      );
+      expect(called).toBe(1);
+      expect(container.firstChild).toMatchSnapshot();
     });
 
     // https://github.com/ant-design/ant-design/issues/11689#issuecomment-411712770
-    it('with expandedKeys', () => {
+    it('with expandedKeys', async () => {
       let called = 0;
       const keys = {};
 
@@ -652,28 +643,29 @@ describe('Tree Props', () => {
         });
       };
 
-      const tree = mount(
-        <Tree loadData={loadData} expandedKeys={['0', '1', '2']}>
+      const renderTree = (props?: any) => (
+        <Tree loadData={loadData} expandedKeys={['0', '1', '2']} {...props}>
           <TreeNode key="0" />
           <TreeNode key="1" />
           <TreeNode key="2" />
-        </Tree>,
+        </Tree>
       );
+      const { rerender } = render(renderTree());
 
-      tree.setProps({ expandedKeys: ['0', '1', '2'] });
+      rerender(renderTree({ expandedKeys: ['0', '1', '2'] }));
 
-      return timeoutPromise().then(() => {
-        expect(called).toBe(3);
-        expect(keys[0]).toBe(1);
-        expect(keys[1]).toBe(1);
-        expect(keys[2]).toBe(1);
-      });
+      await timeoutPromise();
+
+      expect(called).toBe(3);
+      expect(keys[0]).toBe(1);
+      expect(keys[1]).toBe(1);
+      expect(keys[2]).toBe(1);
     });
 
-    it('with defaultExpandedKeys', () => {
+    it('with defaultExpandedKeys', async () => {
       let called = 0;
       const keys = {};
-      const loadData = ({ props: { eventKey } }) => {
+      const loadData: any = ({ props: { eventKey } }) => {
         keys[eventKey] = (keys[eventKey] || 0) + 1;
 
         return new Promise(() => {
@@ -681,7 +673,7 @@ describe('Tree Props', () => {
         });
       };
 
-      const wrapper = mount(
+      const { container } = render(
         <Tree loadData={loadData} defaultExpandedKeys={['0', '1', '2']}>
           <TreeNode key="0" />
           <TreeNode key="1" />
@@ -690,28 +682,27 @@ describe('Tree Props', () => {
       );
 
       // Do not trigger loadData
-      wrapper.find('.rc-tree-switcher').at(0).simulate('click');
-      wrapper.find('.rc-tree-switcher').at(0).simulate('click');
-
-      return timeoutPromise().then(() => {
-        expect(called).toBe(3);
-        expect(keys[0]).toBe(1);
-        expect(keys[1]).toBe(1);
-        expect(keys[2]).toBe(1);
-      });
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
+
+      await timeoutPromise();
+
+      expect(called).toBe(3);
+      expect(keys[0]).toBe(1);
+      expect(keys[1]).toBe(1);
+      expect(keys[2]).toBe(1);
     });
 
     // https://github.com/ant-design/ant-design/issues/12217
     it('node has false isLeaf & no loadData function', () => {
       const onExpand = jest.fn();
-      const wrapper = mount(
+      const { container } = render(
         <Tree onExpand={onExpand}>
           <TreeNode key="0" isLeaf={false} />
         </Tree>,
       );
 
-      const switcher = wrapper.find('.rc-tree-switcher');
-      switcher.simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
 
       expect(onExpand).toHaveBeenCalled();
 
@@ -763,14 +754,14 @@ describe('Tree Props', () => {
         }
       }
 
-      const wrapper = mount(<Test />);
+      const { container } = render(<Test />);
 
       // Parent click
-      wrapper.find('.rc-tree-switcher').simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
 
       setTimeout(() => {
         // Child click
-        wrapper.find('.rc-tree-switcher').at(1).simulate('click');
+        fireEvent.click(container.querySelectorAll('.rc-tree-switcher')[1]);
 
         setTimeout(() => {
           expect(count).toBe(2);
@@ -780,7 +771,7 @@ describe('Tree Props', () => {
     });
 
     it('reject load', async () => {
-      const wrapper = mount(
+      const { container } = render(
         <Tree
           loadData={() => Promise.reject()}
           expandedKeys={['parent']}
@@ -794,10 +785,13 @@ describe('Tree Props', () => {
       );
 
       // Do delay
-      await timeoutPromise();
+      for (let i = 0; i < 20; i += 1) {
+        await act(async () => {
+          await timeoutPromise();
+        });
+      }
 
-      wrapper.update();
-      expect(wrapper.exists('.rc-tree-icon_loading')).toBeFalsy();
+      expect(container.querySelector('.rc-tree-icon_loading')).toBeFalsy();
 
       expect(errorSpy()).toHaveBeenCalledWith(
         'Warning: Retry for `loadData` many times but still failed. No more retry.',
@@ -807,7 +801,7 @@ describe('Tree Props', () => {
 
   it('icon', () => {
     // Node icon has much higher priority
-    const wrapper = render(
+    const { container } = render(
       <Tree defaultExpandAll icon={<span>ROOT ICON</span>}>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" icon={<span>CUSTOMIZE ICON</span>} />
@@ -815,13 +809,13 @@ describe('Tree Props', () => {
       </Tree>,
     );
 
-    expect(renderToJson(wrapper)).toMatchSnapshot();
+    expect(container.firstChild).toMatchSnapshot();
   });
 
   it('onClick', () => {
     const onClick = jest.fn();
 
-    const wrapper = mount(
+    const { container } = render(
       <Tree onClick={onClick} defaultExpandedKeys={['0-0']}>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" />
@@ -829,21 +823,22 @@ describe('Tree Props', () => {
       </Tree>,
     );
 
-    const parentNode = wrapper.find(InternalTreeNode).first();
-    const targetNode = parentNode.find(InternalTreeNode).last();
-
     // Select leaf
-    const node = convertNodePropsToEventData(targetNode.props());
-    targetNode.find('.rc-tree-node-content-wrapper').simulate('click');
+    fireEvent.click(container.querySelectorAll('.rc-tree-node-content-wrapper')[1]);
 
-    expect(onClick).toHaveBeenCalledWith(expect.objectContaining({}), node);
+    expect(onClick).toHaveBeenCalledWith(
+      expect.objectContaining({}),
+      expect.objectContaining({
+        key: '0-0-0',
+      }),
+    );
   });
 
   it('onDoubleClick', () => {
     const onClick = jest.fn();
     const onDoubleClick = jest.fn();
 
-    const wrapper = mount(
+    const { container } = render(
       <Tree onClick={onClick} onDoubleClick={onDoubleClick} defaultExpandedKeys={['0-0']}>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" />
@@ -851,22 +846,23 @@ describe('Tree Props', () => {
       </Tree>,
     );
 
-    const parentNode = wrapper.find(InternalTreeNode).first();
-    const targetNode = parentNode.find(InternalTreeNode).last();
-
     // Select leaf
-    const node = convertNodePropsToEventData(targetNode.props());
-    targetNode.find('.rc-tree-node-content-wrapper').simulate('doubleclick');
+    fireEvent.doubleClick(container.querySelectorAll('.rc-tree-node-content-wrapper')[1]);
 
     expect(onClick).not.toHaveBeenCalled();
-    expect(onDoubleClick).toHaveBeenCalledWith(expect.objectContaining({}), node);
+    expect(onDoubleClick).toHaveBeenCalledWith(
+      expect.objectContaining({}),
+      expect.objectContaining({
+        key: '0-0-0',
+      }),
+    );
   });
 
   it('onContextMenu', () => {
     const onClick = jest.fn();
     const onContextMenu = jest.fn();
 
-    const wrapper = mount(
+    const { container } = render(
       <Tree onClick={onClick} onContextMenu={onContextMenu} defaultExpandedKeys={['0-0']}>
         <TreeNode key="0-0">
           <TreeNode key="0-0-0" />
@@ -874,11 +870,8 @@ describe('Tree Props', () => {
       </Tree>,
     );
 
-    const parentNode = wrapper.find(InternalTreeNode).first();
-    const targetNode = parentNode.find(InternalTreeNode).last();
-
     // Select leaf
-    targetNode.find('.rc-tree-node-content-wrapper').simulate('contextmenu');
+    fireEvent.contextMenu(container.querySelectorAll('.rc-tree-node-content-wrapper')[1]);
 
     expect(onClick).not.toHaveBeenCalled();
     expect(onContextMenu).toHaveBeenCalled();
@@ -889,20 +882,22 @@ describe('Tree Props', () => {
       const loadData = jest.fn(() => Promise.resolve());
       const onLoad = jest.fn();
 
-      const wrapper = mount(
+      const { container } = render(
         <Tree loadedKeys={['0-0']} loadData={loadData} onLoad={onLoad}>
           <TreeNode key="0-0" />
         </Tree>,
       );
 
-      wrapper.find('.rc-tree-switcher').simulate('click');
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
       expect(loadData).not.toHaveBeenCalled();
       expect(onLoad).not.toHaveBeenCalled();
     });
 
     it('reset loadedKeys', () => {
       class FakePromise {
-        constructor(val) {
+        val: any;
+
+        constructor(val?: any) {
           this.val = val;
         }
 
@@ -914,25 +909,38 @@ describe('Tree Props', () => {
         catch = () => {};
       }
 
-      let wrapper;
+      // eslint-disable-next-line prefer-const
+      let cacheRerender: any;
+      // eslint-disable-next-line prefer-const
+      let renderTree: any;
 
-      const loadData = jest.fn(() => new FakePromise());
       const onLoad = jest.fn(() => {
-        wrapper.setProps({ loadedKeys: ['0-0'] });
+        cacheRerender(renderTree({ loadedKeys: ['0-0'] }));
       });
 
-      wrapper = mount(
-        <Tree loadedKeys={['0-0']} loadData={loadData} onLoad={onLoad}>
+      const loadData: any = jest.fn(() => new FakePromise());
+      renderTree = (props?: any) => (
+        <Tree loadedKeys={['0-0']} loadData={loadData} onLoad={onLoad} {...props}>
           <TreeNode key="0-0" />
-        </Tree>,
+        </Tree>
+      );
+
+      const { rerender, container } = render(renderTree());
+      cacheRerender = rerender;
+
+      rerender(renderTree({ loadedKeys: [] }));
+
+      fireEvent.click(container.querySelector('.rc-tree-switcher'));
+      expect(loadData).toHaveBeenCalledWith(
+        expect.objectContaining({
+          key: '0-0',
+        }),
       );
-      wrapper.setProps({ loadedKeys: [] });
-      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
-      wrapper.find('.rc-tree-switcher').simulate('click');
-      expect(loadData).toHaveBeenCalledWith(node);
       expect(onLoad).toHaveBeenCalledWith(['0-0'], {
         event: 'load',
-        node,
+        node: expect.objectContaining({
+          key: '0-0',
+        }),
       });
     });
   });
@@ -957,49 +965,47 @@ describe('Tree Props', () => {
         ],
       },
     ];
-    const wrapper = mount(<Tree treeData={treeData} defaultExpandAll />);
-    expect(wrapper.render()).toMatchSnapshot();
+    const { container, rerender } = render(<Tree treeData={treeData} defaultExpandAll />);
+    expect(container.firstChild).toMatchSnapshot();
 
-    const newTreeData = [{ key: 'K0', title: 'T0' }];
-    wrapper.setProps({ treeData: newTreeData });
-    wrapper.update();
-    expect(wrapper.render()).toMatchSnapshot();
+    rerender(<Tree treeData={[{ key: 'K0', title: 'T0' }]} defaultExpandAll />);
+    expect(container.firstChild).toMatchSnapshot();
   });
 
   describe('disabled', () => {
     it('basic', () => {
-      const wrapper = render(
+      const { container } = render(
         <Tree defaultExpandAll disabled>
           <TreeNode key="0-0" />
         </Tree>,
       );
-      expect(wrapper).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
     });
 
     it('treeNode should disabled when tree disabled', () => {
-      const wrapper = render(
+      const { container } = render(
         <Tree defaultExpandAll disabled>
           <TreeNode key="0-0" disabled={false} />
         </Tree>,
       );
-      expect(wrapper).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
     });
   });
 
   describe('data and aria props', () => {
     it('renders data attributes', () => {
-      const wrapper = render(<Tree data-test="tree" />);
-      expect(renderToJson(wrapper)).toMatchSnapshot();
+      const { container } = render(<Tree data-test="tree" />);
+      expect(container.firstChild).toMatchSnapshot();
     });
 
     it('renders aria attributes', () => {
-      const wrapper = render(<Tree aria-label="name" />);
-      expect(renderToJson(wrapper)).toMatchSnapshot();
+      const { container } = render(<Tree aria-label="name" />);
+      expect(container.firstChild).toMatchSnapshot();
     });
   });
 
   describe('custom switcher icon', () => {
-    function switcherIcon(text, testLeaf) {
+    function switcherIcon(text: React.ReactNode, testLeaf?: boolean) {
       const sfc = ({ isLeaf }) => {
         if (testLeaf) {
           return isLeaf ? <span>{text}</span> : null;
@@ -1009,8 +1015,9 @@ describe('Tree Props', () => {
 
       return sfc;
     }
+
     it('switcher icon', () => {
-      const wrapper = render(
+      const { container } = render(
         <Tree defaultExpandAll switcherIcon={switcherIcon('switcherIcon')}>
           <TreeNode key="0-0" />
           <TreeNode key="0-1" switcherIcon={switcherIcon('switcherIconFromNode0-1')}>
@@ -1019,11 +1026,11 @@ describe('Tree Props', () => {
           </TreeNode>
         </Tree>,
       );
-      expect(wrapper).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
     });
 
     it('switcher leaf icon', () => {
-      const wrapper = render(
+      const { container } = render(
         <Tree defaultExpandAll switcherIcon={switcherIcon('switcherLeafIcon', true)}>
           <TreeNode key="0-0" />
           <TreeNode key="0-1" switcherIcon={switcherIcon('switcherLeafIconFromNode0-1', true)} />
@@ -1037,19 +1044,19 @@ describe('Tree Props', () => {
           <TreeNode key="0-3" />
         </Tree>,
       );
-      expect(wrapper).toMatchSnapshot();
+      expect(container.firstChild).toMatchSnapshot();
     });
   });
 
   it('should style work', () => {
     const style = { background: 'red' };
-    const wrapper = mount(<Tree style={style} />);
-    expect(wrapper.props().style).toEqual(style);
+    const { container } = render(<Tree style={style} />);
+    expect(container.querySelector('.rc-tree-list')).toHaveStyle(style);
   });
 
   it('titleRender', () => {
-    const wrapper = mount(
-      <Tree
+    const { container } = render(
+      <Tree<FieldDataNode<{ value: string; title?: any }>>
         defaultExpandAll
         titleRender={({ value }) => <span className="bamboo-span">{value}</span>}
         treeData={[
@@ -1059,7 +1066,11 @@ describe('Tree Props', () => {
       />,
     );
 
-    expect(wrapper.find('.rc-tree-title').first().find('.light-span').text()).toEqual('light');
-    expect(wrapper.find('.rc-tree-title').last().find('.bamboo-span').text()).toEqual('bamboo');
+    expect(
+      container.querySelectorAll('.rc-tree-title')[0].querySelector('.light-span').textContent,
+    ).toEqual('light');
+    expect(
+      container.querySelectorAll('.rc-tree-title')[1].querySelector('.bamboo-span').textContent,
+    ).toEqual('bamboo');
   });
 });
diff --git a/tests/__snapshots__/Tree.spec.js.snap b/tests/__snapshots__/Tree.spec.tsx.snap
similarity index 96%
rename from tests/__snapshots__/Tree.spec.js.snap
rename to tests/__snapshots__/Tree.spec.tsx.snap
index 72ef9943..996ebd9e 100644
--- a/tests/__snapshots__/Tree.spec.js.snap
+++ b/tests/__snapshots__/Tree.spec.tsx.snap
@@ -8,7 +8,7 @@ exports[`Tree Basic check basic render 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -16,7 +16,7 @@ exports[`Tree Basic check basic render 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -28,7 +28,7 @@ exports[`Tree Basic check basic render 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -36,7 +36,7 @@ exports[`Tree Basic check basic render 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -583,7 +583,7 @@ exports[`Tree Basic renders correctly 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -591,7 +591,7 @@ exports[`Tree Basic renders correctly 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -603,7 +603,7 @@ exports[`Tree Basic renders correctly 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -611,7 +611,7 @@ exports[`Tree Basic renders correctly 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -795,7 +795,7 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -803,7 +803,7 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -815,7 +815,7 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -823,7 +823,7 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
diff --git a/tests/__snapshots__/TreeNodeProps.spec.js.snap b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
similarity index 90%
rename from tests/__snapshots__/TreeNodeProps.spec.js.snap
rename to tests/__snapshots__/TreeNodeProps.spec.tsx.snap
index 1b05ed68..6ba53bca 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
@@ -8,7 +8,7 @@ exports[`TreeNode Props className 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -16,7 +16,7 @@ exports[`TreeNode Props className 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -28,7 +28,7 @@ exports[`TreeNode Props className 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -36,7 +36,7 @@ exports[`TreeNode Props className 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -198,7 +198,7 @@ exports[`TreeNode Props customize icon component 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -206,7 +206,7 @@ exports[`TreeNode Props customize icon component 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -218,7 +218,7 @@ exports[`TreeNode Props customize icon component 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -226,7 +226,7 @@ exports[`TreeNode Props customize icon component 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -273,7 +273,7 @@ exports[`TreeNode Props customize icon element 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -281,7 +281,7 @@ exports[`TreeNode Props customize icon element 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -293,7 +293,7 @@ exports[`TreeNode Props customize icon element 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -301,7 +301,7 @@ exports[`TreeNode Props customize icon element 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -348,7 +348,7 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -356,7 +356,7 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -368,7 +368,7 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -376,7 +376,7 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -416,7 +416,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -424,7 +424,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -436,7 +436,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -444,7 +444,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -611,7 +611,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -619,7 +619,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -631,7 +631,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -639,7 +639,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -806,7 +806,7 @@ exports[`TreeNode Props isLeaf 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -814,7 +814,7 @@ exports[`TreeNode Props isLeaf 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -826,7 +826,7 @@ exports[`TreeNode Props isLeaf 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -834,7 +834,7 @@ exports[`TreeNode Props isLeaf 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -901,7 +901,7 @@ exports[`TreeNode Props isLeaf 2`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -909,7 +909,7 @@ exports[`TreeNode Props isLeaf 2`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -921,7 +921,7 @@ exports[`TreeNode Props isLeaf 2`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -929,7 +929,7 @@ exports[`TreeNode Props isLeaf 2`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -1002,7 +1002,7 @@ exports[`TreeNode Props isLeaf 3`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1010,7 +1010,7 @@ exports[`TreeNode Props isLeaf 3`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -1022,7 +1022,7 @@ exports[`TreeNode Props isLeaf 3`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -1030,7 +1030,7 @@ exports[`TreeNode Props isLeaf 3`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
diff --git a/tests/__snapshots__/TreeProps.spec.js.snap b/tests/__snapshots__/TreeProps.spec.tsx.snap
similarity index 90%
rename from tests/__snapshots__/TreeProps.spec.js.snap
rename to tests/__snapshots__/TreeProps.spec.tsx.snap
index abe1b745..fbd292a6 100644
--- a/tests/__snapshots__/TreeProps.spec.js.snap
+++ b/tests/__snapshots__/TreeProps.spec.tsx.snap
@@ -115,7 +115,7 @@ exports[`Tree Props checkable default 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -123,7 +123,7 @@ exports[`Tree Props checkable default 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -135,7 +135,7 @@ exports[`Tree Props checkable default 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -143,7 +143,7 @@ exports[`Tree Props checkable default 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -222,7 +222,7 @@ exports[`Tree Props checkable without selectable 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -230,7 +230,7 @@ exports[`Tree Props checkable without selectable 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -242,7 +242,7 @@ exports[`Tree Props checkable without selectable 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -250,7 +250,7 @@ exports[`Tree Props checkable without selectable 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -329,7 +329,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -337,7 +337,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -349,7 +349,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -357,7 +357,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -490,7 +490,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -498,7 +498,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -510,7 +510,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -518,7 +518,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -719,7 +719,7 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -727,7 +727,7 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -740,7 +740,7 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
   <div
     aria-label="name"
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -748,7 +748,7 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         />
       </div>
     </div>
@@ -764,7 +764,7 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -772,7 +772,7 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -785,7 +785,7 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
   <div
     class="rc-tree-list"
     data-test="tree"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -793,7 +793,7 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         />
       </div>
     </div>
@@ -809,7 +809,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -817,7 +817,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -829,7 +829,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -837,7 +837,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -911,7 +911,7 @@ exports[`Tree Props disabled basic 1`] = `
     <input
       aria-label="for screen reader"
       disabled=""
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -919,7 +919,7 @@ exports[`Tree Props disabled basic 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -931,7 +931,7 @@ exports[`Tree Props disabled basic 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -939,7 +939,7 @@ exports[`Tree Props disabled basic 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -983,7 +983,7 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
     <input
       aria-label="for screen reader"
       disabled=""
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -991,7 +991,7 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -1003,7 +1003,7 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -1011,7 +1011,7 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -1054,7 +1054,7 @@ exports[`Tree Props icon 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1062,7 +1062,7 @@ exports[`Tree Props icon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -1074,7 +1074,7 @@ exports[`Tree Props icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -1082,7 +1082,7 @@ exports[`Tree Props icon 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -1472,7 +1472,7 @@ exports[`Tree Props multiple 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1480,7 +1480,7 @@ exports[`Tree Props multiple 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -1492,7 +1492,7 @@ exports[`Tree Props multiple 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -1500,7 +1500,7 @@ exports[`Tree Props multiple 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -1573,7 +1573,7 @@ exports[`Tree Props prefixCls 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1581,7 +1581,7 @@ exports[`Tree Props prefixCls 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -1593,7 +1593,7 @@ exports[`Tree Props prefixCls 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -1601,7 +1601,7 @@ exports[`Tree Props prefixCls 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         />
       </div>
     </div>
@@ -1617,7 +1617,7 @@ exports[`Tree Props prefixCls 2`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1625,7 +1625,7 @@ exports[`Tree Props prefixCls 2`] = `
   <div
     aria-hidden="true"
     class="test-prefix-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="test-prefix-indent"
@@ -1637,7 +1637,7 @@ exports[`Tree Props prefixCls 2`] = `
   </div>
   <div
     class="test-prefix-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="test-prefix-list-holder"
@@ -1645,7 +1645,7 @@ exports[`Tree Props prefixCls 2`] = `
       <div>
         <div
           class="test-prefix-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         />
       </div>
     </div>
@@ -1661,7 +1661,7 @@ exports[`Tree Props selectable with selectable 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1669,7 +1669,7 @@ exports[`Tree Props selectable with selectable 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -1681,7 +1681,7 @@ exports[`Tree Props selectable with selectable 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -1689,7 +1689,7 @@ exports[`Tree Props selectable with selectable 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -1762,7 +1762,7 @@ exports[`Tree Props selectable without selectable 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1770,7 +1770,7 @@ exports[`Tree Props selectable without selectable 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -1782,7 +1782,7 @@ exports[`Tree Props selectable without selectable 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -1790,7 +1790,7 @@ exports[`Tree Props selectable without selectable 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -1863,7 +1863,7 @@ exports[`Tree Props showIcon 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1871,7 +1871,7 @@ exports[`Tree Props showIcon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -1883,7 +1883,7 @@ exports[`Tree Props showIcon 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -1891,7 +1891,7 @@ exports[`Tree Props showIcon 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -1960,7 +1960,7 @@ exports[`Tree Props showIcon 2`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -1968,7 +1968,7 @@ exports[`Tree Props showIcon 2`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -1980,7 +1980,7 @@ exports[`Tree Props showIcon 2`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -1988,7 +1988,7 @@ exports[`Tree Props showIcon 2`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -2051,7 +2051,7 @@ exports[`Tree Props showIcon 3`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -2059,7 +2059,7 @@ exports[`Tree Props showIcon 3`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -2071,7 +2071,7 @@ exports[`Tree Props showIcon 3`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -2079,7 +2079,7 @@ exports[`Tree Props showIcon 3`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         >
           <div
             aria-grabbed="false"
@@ -2245,7 +2245,7 @@ exports[`Tree Props showLine 1`] = `
   <div>
     <input
       aria-label="for screen reader"
-      style="width:0;height:0;display:flex;overflow:hidden;opacity:0;border:0;padding:0;margin:0"
+      style="width: 0px; height: 0px; display: flex; overflow: hidden; opacity: 0; border: 0px; padding: 0px; margin: 0px;"
       tabindex="0"
       value=""
     />
@@ -2253,7 +2253,7 @@ exports[`Tree Props showLine 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position:absolute;pointer-events:none;visibility:hidden;height:0;overflow:hidden"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
   >
     <div
       class="rc-tree-indent"
@@ -2265,7 +2265,7 @@ exports[`Tree Props showLine 1`] = `
   </div>
   <div
     class="rc-tree-list"
-    style="position:relative"
+    style="position: relative;"
   >
     <div
       class="rc-tree-list-holder"
@@ -2273,7 +2273,7 @@ exports[`Tree Props showLine 1`] = `
       <div>
         <div
           class="rc-tree-list-holder-inner"
-          style="display:flex;flex-direction:column"
+          style="display: flex; flex-direction: column;"
         />
       </div>
     </div>
diff --git a/tests/setupFilesAfterEnv.js b/tests/setupFilesAfterEnv.js
new file mode 100644
index 00000000..7b0828bf
--- /dev/null
+++ b/tests/setupFilesAfterEnv.js
@@ -0,0 +1 @@
+import '@testing-library/jest-dom';
diff --git a/tests/type.spec.tsx b/tests/type.spec.tsx
index 6bf8f7fd..9e591409 100644
--- a/tests/type.spec.tsx
+++ b/tests/type.spec.tsx
@@ -1,6 +1,6 @@
 /* eslint-disable no-undef, react/no-multi-comp */
 import React from 'react';
-import { mount } from 'enzyme';
+import { render } from '@testing-library/react';
 import Tree, { BasicDataNode } from '../src';
 
 describe('Tree.TypeScript', () => {
@@ -11,7 +11,7 @@ describe('Tree.TypeScript', () => {
       list?: DataType[];
     }
 
-    const wrapper = mount(
+    render(
       <Tree<DataType>
         treeData={[
           {
@@ -22,7 +22,5 @@ describe('Tree.TypeScript', () => {
         ]}
       />,
     );
-
-    expect(wrapper).toBeTruthy();
   });
 });
diff --git a/tests/util.js b/tests/util.ts
similarity index 96%
rename from tests/util.js
rename to tests/util.ts
index a06da17d..65ef12e1 100644
--- a/tests/util.js
+++ b/tests/util.ts
@@ -1,7 +1,7 @@
 import { ReactWrapper } from 'enzyme';
 import { Component } from 'react';
 
-export function objectMatcher(item) {
+export function objectMatcher(item: object) {
   const result = Array.isArray(item) ? [] : {};
 
   Object.keys(item).forEach(key => {

From f6e5c832ea9941dd2e5f3ec1fb913883683d382a Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Fri, 13 May 2022 21:41:57 +0800
Subject: [PATCH 259/397] 5.6.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index be5770fd..9240ed1b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.1",
+  "version": "5.6.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From d9ab8e7d08c2bc0efdb71a5ea20e73ab1d355694 Mon Sep 17 00:00:00 2001
From: Heaven <18418010+NE-SmallTown@users.noreply.github.com>
Date: Wed, 18 May 2022 09:17:35 +0800
Subject: [PATCH 260/397] Fix: fix 'expandAction' ts error, should be optional
 (#584)

---
 src/Tree.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 0f8d2f99..9ed41ee4 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -94,7 +94,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   showIcon?: boolean;
   icon?: IconType;
   selectable?: boolean;
-  expandAction: ExpandAction;
+  expandAction?: ExpandAction;
   disabled?: boolean;
   multiple?: boolean;
   checkable?: boolean | React.ReactNode;

From c27fd78999991ebfdac24863c708d7c08e660376 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Wed, 18 May 2022 09:20:42 +0800
Subject: [PATCH 261/397] 5.6.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 9240ed1b..2b88ca16 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.2",
+  "version": "5.6.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 1c39c28bced8412630f71cc68050a2d0c81a851b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 31 May 2022 15:13:12 +0800
Subject: [PATCH 262/397] chore: More proper ts def (#588)

* chore: part of

* chore: all ts

* chore: rest ts
---
 src/NodeList.tsx      |  8 ++--
 src/Tree.tsx          | 86 ++++++++++++++++++++++---------------------
 src/contextTypes.ts   | 50 ++++++++++++++-----------
 src/interface.tsx     | 24 ++++++------
 src/util.tsx          |  2 +-
 src/utils/treeUtil.ts | 17 +++++----
 6 files changed, 100 insertions(+), 87 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index bd6c4a5a..6a8a3a3f 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -54,13 +54,13 @@ export interface NodeListRef {
   getIndentWidth: () => number;
 }
 
-interface NodeListProps {
+interface NodeListProps<TreeDataType> {
   prefixCls: string;
   style: React.CSSProperties;
-  data: FlattenNode[];
+  data: FlattenNode<TreeDataType>[];
   motion: any;
   focusable?: boolean;
-  activeItem: FlattenNode;
+  activeItem: FlattenNode<TreeDataType>;
   focused?: boolean;
   tabIndex: number;
   checkable?: boolean;
@@ -126,7 +126,7 @@ function getAccessibilityPath(item: FlattenNode): string {
   return path;
 }
 
-const NodeList = React.forwardRef<NodeListRef, NodeListProps>((props, ref) => {
+const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref) => {
   const {
     prefixCls,
     data,
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 9ed41ee4..9cd27b27 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -55,7 +55,7 @@ const MAX_RETRY_TIMES = 10;
 
 interface CheckInfo<TreeDataType extends BasicDataNode = DataNode> {
   event: 'check';
-  node: EventDataNode;
+  node: EventDataNode<TreeDataType>;
   checked: boolean;
   nativeEvent: MouseEvent;
   checkedNodes: TreeDataType[];
@@ -128,7 +128,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   onExpand?: (
     expandedKeys: Key[],
     info: {
-      node: EventDataNode;
+      node: EventDataNode<TreeDataType>;
       expanded: boolean;
       nativeEvent: MouseEvent;
     },
@@ -142,7 +142,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
     info: {
       event: 'select';
       selected: boolean;
-      node: EventDataNode;
+      node: EventDataNode<TreeDataType>;
       selectedNodes: TreeDataType[];
       nativeEvent: MouseEvent;
     },
@@ -151,22 +151,22 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
     loadedKeys: Key[],
     info: {
       event: 'load';
-      node: EventDataNode;
+      node: EventDataNode<TreeDataType>;
     },
   ) => void;
-  loadData?: (treeNode: EventDataNode) => Promise<any>;
+  loadData?: (treeNode: EventDataNode<TreeDataType>) => Promise<any>;
   loadedKeys?: Key[];
-  onMouseEnter?: (info: NodeMouseEventParams) => void;
-  onMouseLeave?: (info: NodeMouseEventParams) => void;
-  onRightClick?: (info: { event: React.MouseEvent; node: EventDataNode }) => void;
-  onDragStart?: (info: NodeDragEventParams) => void;
-  onDragEnter?: (info: NodeDragEventParams & { expandedKeys: Key[] }) => void;
-  onDragOver?: (info: NodeDragEventParams) => void;
-  onDragLeave?: (info: NodeDragEventParams) => void;
-  onDragEnd?: (info: NodeDragEventParams) => void;
+  onMouseEnter?: (info: NodeMouseEventParams<TreeDataType>) => void;
+  onMouseLeave?: (info: NodeMouseEventParams<TreeDataType>) => void;
+  onRightClick?: (info: { event: React.MouseEvent; node: EventDataNode<TreeDataType> }) => void;
+  onDragStart?: (info: NodeDragEventParams<TreeDataType>) => void;
+  onDragEnter?: (info: NodeDragEventParams<TreeDataType> & { expandedKeys: Key[] }) => void;
+  onDragOver?: (info: NodeDragEventParams<TreeDataType>) => void;
+  onDragLeave?: (info: NodeDragEventParams<TreeDataType>) => void;
+  onDragEnd?: (info: NodeDragEventParams<TreeDataType>) => void;
   onDrop?: (
-    info: NodeDragEventParams & {
-      dragNode: EventDataNode;
+    info: NodeDragEventParams<TreeDataType> & {
+      dragNode: EventDataNode<TreeDataType>;
       dragNodesKeys: Key[];
       dropPosition: number;
       dropToGap: boolean;
@@ -177,7 +177,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
    * Do not use in your production code directly since this will be refactor.
    */
   onActiveChange?: (key: Key) => void;
-  filterTreeNode?: (treeNode: EventDataNode) => boolean;
+  filterTreeNode?: (treeNode: EventDataNode<TreeDataType>) => boolean;
   motion?: any;
   switcherIcon?: IconType;
 
@@ -217,8 +217,8 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
   dropAllowed: boolean;
   dragOverNodeKey: Key | null;
 
-  treeData: DataNode[];
-  flattenNodes: FlattenNode[];
+  treeData: TreeDataType[];
+  flattenNodes: FlattenNode<TreeDataType>[];
 
   focused: boolean;
   activeKey: Key;
@@ -462,7 +462,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     return newState;
   }
 
-  onNodeDragStart: NodeDragEventHandler<HTMLDivElement, TreeDataType> = (event, node) => {
+  onNodeDragStart: NodeDragEventHandler<TreeDataType, HTMLDivElement> = (event, node) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
     const { eventKey } = node.props;
@@ -485,7 +485,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     window.addEventListener('dragend', this.onWindowDragEnd);
 
-    onDragStart?.({ event, node: convertNodePropsToEventData(node.props) });
+    onDragStart?.({ event, node: convertNodePropsToEventData<TreeDataType>(node.props) });
   };
 
   /**
@@ -679,7 +679,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     onDragOver?.({ event, node: convertNodePropsToEventData(node.props) });
   };
 
-  onNodeDragLeave: NodeDragEventHandler = (event, node) => {
+  onNodeDragLeave: NodeDragEventHandler<TreeDataType> = (event, node) => {
     // if it is outside the droppable area
     // currentMouseOverDroppableNodeKey will be updated in dragenter event when into another droppable receiver.
     if (
@@ -692,7 +692,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     const { onDragLeave } = this.props;
 
-    onDragLeave?.({ event, node: convertNodePropsToEventData(node.props) });
+    onDragLeave?.({ event, node: convertNodePropsToEventData<TreeDataType>(node.props) });
   };
 
   // since stopPropagation() is called in treeNode
@@ -703,7 +703,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
   };
 
   // if onNodeDragEnd is called, onWindowDragEnd won't be called since stopPropagation() is called
-  onNodeDragEnd: NodeDragEventHandler = (event, node) => {
+  onNodeDragEnd: NodeDragEventHandler<TreeDataType> = (event, node) => {
     const { onDragEnd } = this.props;
     this.setState({
       dragOverNodeKey: null,
@@ -711,7 +711,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     this.cleanDragState();
 
-    onDragEnd?.({ event, node: convertNodePropsToEventData(node.props) });
+    onDragEnd?.({ event, node: convertNodePropsToEventData<TreeDataType>(node.props) });
 
     this.dragNode = null;
   };
@@ -733,7 +733,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     const abstractDropNodeProps = {
       ...getTreeNodeProps(dropTargetKey, this.getTreeNodeRequiredProps()),
-      active: this.getActiveItem()?.data.key === dropTargetKey,
+      active: this.getActiveItem()?.key === dropTargetKey,
       data: this.state.keyEntities[dropTargetKey].node,
     };
     const dropToChild = dragChildrenKeys.indexOf(dropTargetKey) !== -1;
@@ -795,7 +795,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     const { expanded, key } = treeNode;
 
     const node = flattenNodes.filter(nodeItem => nodeItem.key === key)[0];
-    const eventNode = convertNodePropsToEventData({
+    const eventNode = convertNodePropsToEventData<TreeDataType>({
       ...getTreeNodeProps(key, this.getTreeNodeRequiredProps()),
       data: node.data,
     });
@@ -824,7 +824,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     onDoubleClick?.(e, treeNode);
   };
 
-  onNodeSelect: NodeMouseEventHandler = (e, treeNode) => {
+  onNodeSelect: NodeMouseEventHandler<TreeDataType> = (e, treeNode) => {
     let { selectedKeys } = this.state;
     const { keyEntities, fieldNames } = this.state;
     const { onSelect, multiple } = this.props;
@@ -864,7 +864,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
   onNodeCheck = (
     e: React.MouseEvent<HTMLSpanElement>,
-    treeNode: EventDataNode,
+    treeNode: EventDataNode<TreeDataType>,
     checked: boolean,
   ) => {
     const {
@@ -945,7 +945,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     onCheck?.(checkedObj, eventObj as CheckInfo<TreeDataType>);
   };
 
-  onNodeLoad = (treeNode: EventDataNode) => {
+  onNodeLoad = (treeNode: EventDataNode<TreeDataType>) => {
     const { key } = treeNode;
 
     const loadPromise = new Promise<void>((resolve, reject) => {
@@ -1013,19 +1013,19 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     return loadPromise;
   };
 
-  onNodeMouseEnter: NodeMouseEventHandler = (event, node) => {
+  onNodeMouseEnter: NodeMouseEventHandler<TreeDataType> = (event, node) => {
     const { onMouseEnter } = this.props;
 
     onMouseEnter?.({ event, node });
   };
 
-  onNodeMouseLeave: NodeMouseEventHandler = (event, node) => {
+  onNodeMouseLeave: NodeMouseEventHandler<TreeDataType> = (event, node) => {
     const { onMouseLeave } = this.props;
 
     onMouseLeave?.({ event, node });
   };
 
-  onNodeContextMenu: NodeMouseEventHandler = (event, node) => {
+  onNodeContextMenu: NodeMouseEventHandler<TreeDataType> = (event, node) => {
     const { onRightClick } = this.props;
     if (onRightClick) {
       event.preventDefault();
@@ -1078,7 +1078,11 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
   setExpandedKeys = (expandedKeys: Key[]) => {
     const { treeData, fieldNames } = this.state;
 
-    const flattenNodes: FlattenNode[] = flattenTreeData(treeData, expandedKeys, fieldNames);
+    const flattenNodes: FlattenNode<TreeDataType>[] = flattenTreeData<TreeDataType>(
+      treeData,
+      expandedKeys,
+      fieldNames,
+    );
     this.setUncontrolledState(
       {
         expandedKeys,
@@ -1088,7 +1092,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     );
   };
 
-  onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode) => {
+  onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode<TreeDataType>) => {
     let { expandedKeys } = this.state;
     const { listChanging, fieldNames } = this.state;
     const { onExpand, loadData } = this.props;
@@ -1130,7 +1134,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
         loadPromise
           .then(() => {
             // [Legacy] Refresh logic
-            const newFlattenTreeData = flattenTreeData(
+            const newFlattenTreeData = flattenTreeData<TreeDataType>(
               this.state.treeData,
               expandedKeys,
               fieldNames,
@@ -1208,7 +1212,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
   };
 
   onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
-    const { activeKey, expandedKeys, checkedKeys } = this.state;
+    const { activeKey, expandedKeys, checkedKeys, fieldNames } = this.state;
     const { onKeyDown, checkable, selectable } = this.props;
 
     // >>>>>>>>>> Direction
@@ -1231,8 +1235,8 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       const treeNodeRequiredProps = this.getTreeNodeRequiredProps();
 
       const expandable =
-        activeItem.data.isLeaf === false || !!(activeItem.data.children || []).length;
-      const eventNode = convertNodePropsToEventData({
+        activeItem.data.isLeaf === false || !!(activeItem.data[fieldNames.children] || []).length;
+      const eventNode = convertNodePropsToEventData<TreeDataType>({
         ...getTreeNodeProps(activeKey, treeNodeRequiredProps),
         data: activeItem.data,
         active: true,
@@ -1245,7 +1249,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
           if (expandable && expandedKeys.includes(activeKey)) {
             this.onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
           } else if (activeItem.parent) {
-            this.onActiveChange(activeItem.parent.data.key);
+            this.onActiveChange(activeItem.parent.key);
           }
           event.preventDefault();
           break;
@@ -1255,7 +1259,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
           if (expandable && !expandedKeys.includes(activeKey)) {
             this.onNodeExpand({} as React.MouseEvent<HTMLDivElement>, eventNode);
           } else if (activeItem.children && activeItem.children.length) {
-            this.onActiveChange(activeItem.children[0].data.key);
+            this.onActiveChange(activeItem.children[0].key);
           }
           event.preventDefault();
           break;
@@ -1295,7 +1299,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
    * Only update the value which is not in props
    */
   setUncontrolledState = (
-    state: Partial<TreeState>,
+    state: Partial<TreeState<TreeDataType>>,
     atomic: boolean = false,
     forceState: Partial<TreeState<TreeDataType>> | null = null,
   ) => {
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 4de053f4..1fff4f21 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -15,25 +15,31 @@ import {
 } from './interface';
 import { DraggableConfig } from './Tree';
 
-export type NodeMouseEventParams<T = HTMLSpanElement> = {
+export type NodeMouseEventParams<
+  TreeDataType extends BasicDataNode = DataNode,
+  T = HTMLSpanElement,
+> = {
   event: React.MouseEvent<T>;
-  node: EventDataNode;
+  node: EventDataNode<TreeDataType>;
 };
-export type NodeDragEventParams<T = HTMLDivElement> = {
+export type NodeDragEventParams<
+  TreeDataType extends BasicDataNode = DataNode,
+  T = HTMLDivElement,
+> = {
   event: React.DragEvent<T>;
-  node: EventDataNode;
+  node: EventDataNode<TreeDataType>;
 };
 
-export type NodeMouseEventHandler<T = HTMLSpanElement> = (
-  e: React.MouseEvent<T>,
-  node: EventDataNode,
-) => void;
+export type NodeMouseEventHandler<
+  TreeDataType extends BasicDataNode = DataNode,
+  T = HTMLSpanElement,
+> = (e: React.MouseEvent<T>, node: EventDataNode<TreeDataType>) => void;
 export type NodeDragEventHandler<
-  T = HTMLDivElement,
   TreeDataType extends BasicDataNode = DataNode,
+  T = HTMLDivElement,
 > = (e: React.DragEvent<T>, node: NodeInstance<TreeDataType>, outsideTree?: boolean) => void;
 
-export interface TreeContextProps {
+export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode> {
   prefixCls: string;
   selectable: boolean;
   showIcon: boolean;
@@ -61,23 +67,23 @@ export interface TreeContextProps {
   dragOverNodeKey: Key | null;
   direction: Direction;
 
-  loadData: (treeNode: EventDataNode) => Promise<void>;
-  filterTreeNode: (treeNode: EventDataNode) => boolean;
+  loadData: (treeNode: EventDataNode<TreeDataType>) => Promise<void>;
+  filterTreeNode: (treeNode: EventDataNode<TreeDataType>) => boolean;
   titleRender?: (node: any) => React.ReactNode;
 
-  onNodeClick: NodeMouseEventHandler;
-  onNodeDoubleClick: NodeMouseEventHandler;
-  onNodeExpand: NodeMouseEventHandler;
-  onNodeSelect: NodeMouseEventHandler;
+  onNodeClick: NodeMouseEventHandler<TreeDataType>;
+  onNodeDoubleClick: NodeMouseEventHandler<TreeDataType>;
+  onNodeExpand: NodeMouseEventHandler<TreeDataType>;
+  onNodeSelect: NodeMouseEventHandler<TreeDataType>;
   onNodeCheck: (
     e: React.MouseEvent<HTMLSpanElement>,
-    treeNode: EventDataNode,
+    treeNode: EventDataNode<TreeDataType>,
     checked: boolean,
   ) => void;
-  onNodeLoad: (treeNode: EventDataNode) => void;
-  onNodeMouseEnter: NodeMouseEventHandler;
-  onNodeMouseLeave: NodeMouseEventHandler;
-  onNodeContextMenu: NodeMouseEventHandler;
+  onNodeLoad: (treeNode: EventDataNode<TreeDataType>) => void;
+  onNodeMouseEnter: NodeMouseEventHandler<TreeDataType>;
+  onNodeMouseLeave: NodeMouseEventHandler<TreeDataType>;
+  onNodeContextMenu: NodeMouseEventHandler<TreeDataType>;
   onNodeDragStart: NodeDragEventHandler<any, any>;
   onNodeDragEnter: NodeDragEventHandler<any, any>;
   onNodeDragOver: NodeDragEventHandler<any, any>;
@@ -86,4 +92,4 @@ export interface TreeContextProps {
   onNodeDrop: NodeDragEventHandler<any, any>;
 }
 
-export const TreeContext: React.Context<TreeContextProps | null> = React.createContext(null);
+export const TreeContext: React.Context<TreeContextProps<any> | null> = React.createContext(null);
diff --git a/src/interface.tsx b/src/interface.tsx
index da0ef4ab..da70291f 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -18,18 +18,18 @@ export interface BasicDataNode {
   style?: React.CSSProperties;
 }
 
-export interface DataNode extends BasicDataNode {
-  children?: DataNode[];
-  key: string | number;
-  title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
-}
-
 /** Provide a wrap type define for developer to wrap with customize fieldNames data type */
 export type FieldDataNode<T, ChildFieldName extends string = 'children'> = BasicDataNode &
   T &
   Partial<Record<ChildFieldName, FieldDataNode<T, ChildFieldName>[]>>;
 
-export interface EventDataNode extends DataNode {
+export type DataNode = FieldDataNode<{
+  key: string | number;
+  title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
+}>;
+
+export type EventDataNode<TreeDataType> = {
+  key: React.Key;
   expanded: boolean;
   selected: boolean;
   checked: boolean;
@@ -41,7 +41,7 @@ export interface EventDataNode extends DataNode {
   dragOverGapBottom: boolean;
   pos: string;
   active: boolean;
-}
+} & TreeDataType;
 
 export type IconType = React.ReactNode | ((props: TreeNodeProps) => React.ReactNode);
 
@@ -78,11 +78,11 @@ export interface DataEntity<TreeDataType extends BasicDataNode = DataNode>
   level: number;
 }
 
-export interface FlattenNode {
-  parent: FlattenNode | null;
-  children: FlattenNode[];
+export interface FlattenNode<TreeDataType extends BasicDataNode = DataNode> {
+  parent: FlattenNode<TreeDataType> | null;
+  children: FlattenNode<TreeDataType>[];
   pos: string;
-  data: DataNode;
+  data: TreeDataType;
   title: React.ReactNode;
   key: Key;
   isStart: boolean[];
diff --git a/src/util.tsx b/src/util.tsx
index c5148f6c..6165bf46 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -97,7 +97,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
     y: number;
   },
   allowDrop: AllowDrop<TreeDataType>,
-  flattenedNodes: FlattenNode[],
+  flattenedNodes: FlattenNode<TreeDataType>[],
   keyEntities: Record<Key, DataEntity<TreeDataType>>,
   expandKeys: Key[],
   direction: Direction,
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 289df048..50a3f637 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -105,11 +105,11 @@ export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
  * @param expandedKeys
  * need expanded keys, provides `true` means all expanded (used in `rc-tree-select`).
  */
-export function flattenTreeData(
-  treeNodeList: DataNode[],
+export function flattenTreeData<TreeDataType extends BasicDataNode = DataNode>(
+  treeNodeList: TreeDataType[],
   expandedKeys: Key[] | true,
   fieldNames: FieldNames,
-): FlattenNode[] {
+): FlattenNode<TreeDataType>[] {
   const {
     _title: fieldTitles,
     key: fieldKey,
@@ -117,9 +117,12 @@ export function flattenTreeData(
   } = fillFieldNames(fieldNames);
 
   const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys);
-  const flattenList: FlattenNode[] = [];
+  const flattenList: FlattenNode<TreeDataType>[] = [];
 
-  function dig(list: DataNode[], parent: FlattenNode = null): FlattenNode[] {
+  function dig(
+    list: TreeDataType[],
+    parent: FlattenNode<TreeDataType> = null,
+  ): FlattenNode<TreeDataType>[] {
     return list.map((treeNode, index) => {
       const pos: string = getPosition(parent ? parent.pos : '0', index);
       const mergedKey = getKey(treeNode[fieldKey], pos);
@@ -135,7 +138,7 @@ export function flattenTreeData(
       }
 
       // Add FlattenDataNode into list
-      const flattenNode: FlattenNode = {
+      const flattenNode: FlattenNode<TreeDataType> = {
         ...omit(treeNode, [...fieldTitles, fieldKey, fieldChildren] as any),
         title: mergedTitle,
         key: mergedKey,
@@ -393,7 +396,7 @@ export function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(
 
 export function convertNodePropsToEventData<TreeDataType extends BasicDataNode = DataNode>(
   props: TreeNodeProps<TreeDataType>,
-): EventDataNode {
+): EventDataNode<TreeDataType> {
   const {
     data,
     expanded,

From 80ebffd3aba1b0f475e7ba345af28e6989e33917 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 31 May 2022 16:06:12 +0800
Subject: [PATCH 263/397] 5.6.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2b88ca16..9bb62891 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.3",
+  "version": "5.6.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From f80557e57257f3714a4b9c1d1ca37a1200907b41 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Mon, 6 Jun 2022 22:44:11 +0800
Subject: [PATCH 264/397] chore: not allow publish on any branch

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2b88ca16..32c7037f 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
     "docs:build": "dumi build",
     "docs:deploy": "gh-pages -d docs-dist",
     "compile": "father build && lessc assets/index.less assets/index.css",
-    "prepublishOnly": "npm run compile && np --yolo --no-publish --any-branch",
+    "prepublishOnly": "npm run compile && np --yolo --no-publish",
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
     "test": "father test",

From 2df935f59d737979a1e3ddd11231d1bd69f4b0b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 7 Jun 2022 19:58:01 +0800
Subject: [PATCH 265/397] fix: Tree should not expand when ctrl key pressed
 (#595)

* chore: not trigger expand when ctrl key pressed

* test: test driven

* test: rm comment
---
 src/Tree.tsx                |   6 +-
 tests/TreeProps.spec.tsx    | 142 -----------------------------
 tests/expandAction.spec.tsx | 177 ++++++++++++++++++++++++++++++++++++
 3 files changed, 182 insertions(+), 143 deletions(-)
 create mode 100644 tests/expandAction.spec.tsx

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 9cd27b27..460a06bf 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -792,7 +792,11 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
   triggerExpandActionExpand: NodeMouseEventHandler = (e, treeNode) => {
     const { expandedKeys, flattenNodes } = this.state;
-    const { expanded, key } = treeNode;
+    const { expanded, key, isLeaf } = treeNode;
+
+    if (isLeaf || e.shiftKey || e.metaKey || e.ctrlKey) {
+      return;
+    }
 
     const node = flattenNodes.filter(nodeItem => nodeItem.key === key)[0];
     const eventNode = convertNodePropsToEventData<TreeDataType>({
diff --git a/tests/TreeProps.spec.tsx b/tests/TreeProps.spec.tsx
index 7578c268..7b1fedfd 100644
--- a/tests/TreeProps.spec.tsx
+++ b/tests/TreeProps.spec.tsx
@@ -176,148 +176,6 @@ describe('Tree Props', () => {
     });
   });
 
-  describe('expandAction with selectable props', () => {
-    it('title expandable when selectable is false and expandAction is "click"', () => {
-      const onClick = jest.fn();
-      const onSelect = jest.fn();
-      const onExpand = jest.fn();
-
-      const { container } = render(
-        <Tree
-          onClick={onClick}
-          onSelect={onSelect}
-          onExpand={onExpand}
-          defaultExpandedKeys={['0-0']}
-          selectable={false}
-          expandAction="click"
-        >
-          <TreeNode title="parent 1" key="0-0">
-            <TreeNode title="leaf 1" key="0-0-0">
-              <TreeNode title="leaf-1" key="0-0-0-0" />
-            </TreeNode>
-
-            <TreeNode title="leaf 2" key="0-1-0">
-              <TreeNode title="leaf-2" key="0-1-0-0" />
-            </TreeNode>
-          </TreeNode>
-        </Tree>,
-      );
-
-      // test trigger expand when click title
-      fireEvent.click(container.querySelector('[title="leaf 1"]'));
-
-      expect(onClick).toHaveBeenCalled();
-      expect(onSelect).not.toHaveBeenCalled();
-      expect(onExpand).toHaveBeenCalledWith(['0-0', '0-0-0'], {
-        expanded: true,
-        node: expect.anything(),
-        nativeEvent: expect.anything(),
-      });
-
-      onClick.mockReset();
-      onSelect.mockReset();
-      onExpand.mockReset();
-
-      // test trigger un-expand when click title again
-      fireEvent.click(container.querySelector('[title="leaf 1"]'));
-
-      expect(onClick).toHaveBeenCalled();
-      expect(onSelect).not.toHaveBeenCalled();
-      expect(onExpand).toHaveBeenCalledWith(['0-0'], {
-        expanded: false,
-        node: expect.anything(),
-        nativeEvent: expect.anything(),
-      });
-    });
-
-    it('title expandable when selectable is false and expandAction is "doubleClick"', () => {
-      const onDoubleClick = jest.fn();
-      const onSelect = jest.fn();
-      const onExpand = jest.fn();
-
-      const { container } = render(
-        <Tree
-          onDoubleClick={onDoubleClick}
-          onSelect={onSelect}
-          onExpand={onExpand}
-          defaultExpandedKeys={['0-0']}
-          selectable={false}
-          expandAction="doubleClick"
-        >
-          <TreeNode title="parent 1" key="0-0">
-            <TreeNode title="leaf 1" key="0-0-0">
-              <TreeNode title="leaf-1" key="0-0-0-0" />
-            </TreeNode>
-
-            <TreeNode title="leaf 2" key="0-1-0">
-              <TreeNode title="leaf-2" key="0-1-0-0" />
-            </TreeNode>
-          </TreeNode>
-        </Tree>,
-      );
-
-      // test trigger expand when double click title
-      fireEvent.doubleClick(container.querySelector('[title="leaf 1"]'));
-
-      expect(onDoubleClick).toHaveBeenCalled();
-      expect(onSelect).not.toHaveBeenCalled();
-      expect(onExpand).toHaveBeenCalledWith(['0-0', '0-0-0'], {
-        expanded: true,
-        node: expect.anything(),
-        nativeEvent: expect.anything(),
-      });
-
-      onDoubleClick.mockReset();
-      onSelect.mockReset();
-      onExpand.mockReset();
-
-      // test trigger un-expand when double click title again
-      fireEvent.doubleClick(container.querySelector('[title="leaf 1"]'));
-
-      expect(onDoubleClick).toHaveBeenCalled();
-      expect(onSelect).not.toHaveBeenCalled();
-      expect(onExpand).toHaveBeenCalledWith(['0-0'], {
-        expanded: false,
-        node: expect.anything(),
-        nativeEvent: expect.anything(),
-      });
-    });
-
-    it('title un-expandable when selectable is false and expandAction is false', () => {
-      const onClick = jest.fn();
-      const onSelect = jest.fn();
-      const onExpand = jest.fn();
-
-      const { container } = render(
-        <Tree
-          onClick={onClick}
-          onSelect={onSelect}
-          onExpand={onExpand}
-          defaultExpandedKeys={['0-0']}
-          selectable={false}
-          expandAction={false}
-        >
-          <TreeNode title="parent 1" key="0-0">
-            <TreeNode title="leaf 1" key="0-0-0">
-              <TreeNode title="leaf-1" key="0-0-0-0" />
-            </TreeNode>
-
-            <TreeNode title="leaf 2" key="0-1-0">
-              <TreeNode title="leaf-2" key="0-1-0-0" />
-            </TreeNode>
-          </TreeNode>
-        </Tree>,
-      );
-
-      // test won't trigger expand when click title if expandAction is false
-      fireEvent.click(container.querySelector('[title="leaf 2"]'));
-
-      expect(onClick).toHaveBeenCalled();
-      expect(onSelect).not.toHaveBeenCalled();
-      expect(onExpand).not.toHaveBeenCalled();
-    });
-  });
-
   // multiple - this prop works with selectable
   it('multiple', () => {
     const handleOnSelect = jest.fn();
diff --git a/tests/expandAction.spec.tsx b/tests/expandAction.spec.tsx
new file mode 100644
index 00000000..97da758f
--- /dev/null
+++ b/tests/expandAction.spec.tsx
@@ -0,0 +1,177 @@
+/* eslint-disable no-undef, react/no-multi-comp */
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import Tree, { TreeNode } from '../src';
+import { spyConsole } from './util';
+
+describe('ExpandAction', () => {
+  spyConsole();
+
+  it('title expandable when selectable is false and expandAction is "click"', () => {
+    const onClick = jest.fn();
+    const onSelect = jest.fn();
+    const onExpand = jest.fn();
+
+    const { container } = render(
+      <Tree
+        onClick={onClick}
+        onSelect={onSelect}
+        onExpand={onExpand}
+        defaultExpandedKeys={['0-0']}
+        selectable={false}
+        expandAction="click"
+      >
+        <TreeNode title="parent 1" key="0-0">
+          <TreeNode title="leaf 1" key="0-0-0">
+            <TreeNode title="leaf-1" key="0-0-0-0" />
+          </TreeNode>
+
+          <TreeNode title="leaf 2" key="0-1-0">
+            <TreeNode title="leaf-2" key="0-1-0-0" />
+          </TreeNode>
+        </TreeNode>
+      </Tree>,
+    );
+
+    // test trigger expand when click title
+    fireEvent.click(container.querySelector('[title="leaf 1"]'));
+
+    expect(onClick).toHaveBeenCalled();
+    expect(onSelect).not.toHaveBeenCalled();
+    expect(onExpand).toHaveBeenCalledWith(['0-0', '0-0-0'], {
+      expanded: true,
+      node: expect.anything(),
+      nativeEvent: expect.anything(),
+    });
+
+    onClick.mockReset();
+    onSelect.mockReset();
+    onExpand.mockReset();
+
+    // test trigger un-expand when click title again
+    fireEvent.click(container.querySelector('[title="leaf 1"]'));
+
+    expect(onClick).toHaveBeenCalled();
+    expect(onSelect).not.toHaveBeenCalled();
+    expect(onExpand).toHaveBeenCalledWith(['0-0'], {
+      expanded: false,
+      node: expect.anything(),
+      nativeEvent: expect.anything(),
+    });
+  });
+
+  it('title expandable when selectable is false and expandAction is "doubleClick"', () => {
+    const onDoubleClick = jest.fn();
+    const onSelect = jest.fn();
+    const onExpand = jest.fn();
+
+    const { container } = render(
+      <Tree
+        onDoubleClick={onDoubleClick}
+        onSelect={onSelect}
+        onExpand={onExpand}
+        defaultExpandedKeys={['0-0']}
+        selectable={false}
+        expandAction="doubleClick"
+      >
+        <TreeNode title="parent 1" key="0-0">
+          <TreeNode title="leaf 1" key="0-0-0">
+            <TreeNode title="leaf-1" key="0-0-0-0" />
+          </TreeNode>
+
+          <TreeNode title="leaf 2" key="0-1-0">
+            <TreeNode title="leaf-2" key="0-1-0-0" />
+          </TreeNode>
+        </TreeNode>
+      </Tree>,
+    );
+
+    // test trigger expand when double click title
+    fireEvent.doubleClick(container.querySelector('[title="leaf 1"]'));
+
+    expect(onDoubleClick).toHaveBeenCalled();
+    expect(onSelect).not.toHaveBeenCalled();
+    expect(onExpand).toHaveBeenCalledWith(['0-0', '0-0-0'], {
+      expanded: true,
+      node: expect.anything(),
+      nativeEvent: expect.anything(),
+    });
+
+    onDoubleClick.mockReset();
+    onSelect.mockReset();
+    onExpand.mockReset();
+
+    // test trigger un-expand when double click title again
+    fireEvent.doubleClick(container.querySelector('[title="leaf 1"]'));
+
+    expect(onDoubleClick).toHaveBeenCalled();
+    expect(onSelect).not.toHaveBeenCalled();
+    expect(onExpand).toHaveBeenCalledWith(['0-0'], {
+      expanded: false,
+      node: expect.anything(),
+      nativeEvent: expect.anything(),
+    });
+  });
+
+  it('title un-expandable when selectable is false and expandAction is false', () => {
+    const onClick = jest.fn();
+    const onSelect = jest.fn();
+    const onExpand = jest.fn();
+
+    const { container } = render(
+      <Tree
+        onClick={onClick}
+        onSelect={onSelect}
+        onExpand={onExpand}
+        defaultExpandedKeys={['0-0']}
+        selectable={false}
+        expandAction={false}
+      >
+        <TreeNode title="parent 1" key="0-0">
+          <TreeNode title="leaf 1" key="0-0-0">
+            <TreeNode title="leaf-1" key="0-0-0-0" />
+          </TreeNode>
+
+          <TreeNode title="leaf 2" key="0-1-0">
+            <TreeNode title="leaf-2" key="0-1-0-0" />
+          </TreeNode>
+        </TreeNode>
+      </Tree>,
+    );
+
+    // test won't trigger expand when click title if expandAction is false
+    fireEvent.click(container.querySelector('[title="leaf 2"]'));
+
+    expect(onClick).toHaveBeenCalled();
+    expect(onSelect).not.toHaveBeenCalled();
+    expect(onExpand).not.toHaveBeenCalled();
+  });
+
+  it('not trigger expand when ctrl pressed', () => {
+    const onClick = jest.fn();
+    const onSelect = jest.fn();
+    const onExpand = jest.fn();
+
+    const { container } = render(
+      <Tree
+        onClick={onClick}
+        onSelect={onSelect}
+        onExpand={onExpand}
+        selectable
+        expandAction={'click'}
+      >
+        <TreeNode title="parent 1" key="0-0">
+          <TreeNode title="leaf-1" key="0-0-0" />
+        </TreeNode>
+      </Tree>,
+    );
+
+    fireEvent.click(container.querySelector('[title="parent 1"]'), {
+      ctrlKey: true,
+    });
+
+    expect(onClick).toHaveBeenCalled();
+    expect(onSelect).toHaveBeenCalled();
+    expect(onExpand).not.toHaveBeenCalled();
+  });
+});

From 0771c1156e51e79c2f2eac5b5962cb832131ef16 Mon Sep 17 00:00:00 2001
From: zombiej <smith3816@gmail.com>
Date: Tue, 7 Jun 2022 20:00:17 +0800
Subject: [PATCH 266/397] 5.6.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 896c7397..8d69f597 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.4",
+  "version": "5.6.5",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From ae4a461378b286aca92fa5855173017cf4d9c81f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=8F=B6=E6=9E=AB?=
 <7971419+crazyair@users.noreply.github.com>
Date: Tue, 28 Jun 2022 12:32:04 +0800
Subject: [PATCH 267/397] fix: ts (#600)

* fix: ts

* fix: ts
---
 src/interface.tsx   | 3 ++-
 tests/type.spec.tsx | 3 +++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/interface.tsx b/src/interface.tsx
index da70291f..bf45a272 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -41,7 +41,8 @@ export type EventDataNode<TreeDataType> = {
   dragOverGapBottom: boolean;
   pos: string;
   active: boolean;
-} & TreeDataType;
+} & TreeDataType &
+  BasicDataNode;
 
 export type IconType = React.ReactNode | ((props: TreeNodeProps) => React.ReactNode);
 
diff --git a/tests/type.spec.tsx b/tests/type.spec.tsx
index 9e591409..7ba1c054 100644
--- a/tests/type.spec.tsx
+++ b/tests/type.spec.tsx
@@ -20,6 +20,9 @@ describe('Tree.TypeScript', () => {
             list: [],
           },
         ]}
+        onSelect={(selectedKeys, info) => {
+          console.log('info', info.node.isLeaf);
+        }}
       />,
     );
   });

From 94e219789da42da675b5ae6201ac53453ad11f37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 26 Jul 2022 17:39:42 +0800
Subject: [PATCH 268/397] fix: clean up when treeNode stop drag (#614)

---
 src/Tree.tsx                 |  2 ++
 tests/TreeDraggable.spec.tsx | 20 ++++++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 460a06bf..a050fafe 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -714,6 +714,8 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     onDragEnd?.({ event, node: convertNodePropsToEventData<TreeDataType>(node.props) });
 
     this.dragNode = null;
+
+    window.removeEventListener('dragend', this.onWindowDragEnd);
   };
 
   onNodeDrop = (event: React.DragEvent<HTMLDivElement>, node, outsideTree: boolean = false) => {
diff --git a/tests/TreeDraggable.spec.tsx b/tests/TreeDraggable.spec.tsx
index 27d063da..124153a1 100644
--- a/tests/TreeDraggable.spec.tsx
+++ b/tests/TreeDraggable.spec.tsx
@@ -1115,4 +1115,24 @@ describe('Tree Draggable', () => {
 
     expect(onDrop).toHaveBeenCalled();
   });
+
+  it('not break of other drop node', () => {
+    const onDragStart = jest.fn();
+    const onDragEnd = jest.fn();
+    const { container } = render(
+      <div>
+        {createTree({ onDragStart, onDragEnd })}
+        <div className="test" />
+      </div>,
+    );
+    const treeNode = container.querySelector('.dragTarget > .rc-tree-node-content-wrapper');
+    fireEvent.dragStart(treeNode);
+    expect(onDragStart).toHaveBeenCalled();
+
+    fireEvent.dragEnd(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+    expect(onDragEnd).toHaveBeenCalled();
+
+    // Should not break
+    fireEvent.dragEnd(container.querySelector('.test'));
+  });
 });

From 2ed2c0943a401e3155bdbf6a57cea08cbfde6980 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 26 Jul 2022 17:40:14 +0800
Subject: [PATCH 269/397] 5.6.6

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8d69f597..8fa1ca9f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.5",
+  "version": "5.6.6",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 41721d8354052819769326117f13a5ea90430176 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 30 Aug 2022 18:04:45 +0800
Subject: [PATCH 270/397] chore(deps-dev): bump @umijs/fabric from 2.12.2 to
 3.0.0 (#612)

Bumps [@umijs/fabric](https://github.com/umijs/fabric) from 2.12.2 to 3.0.0.
- [Release notes](https://github.com/umijs/fabric/releases)
- [Commits](https://github.com/umijs/fabric/compare/v2.12.2...v3.0.0)

---
updated-dependencies:
- dependency-name: "@umijs/fabric"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8fa1ca9f..11d8dec5 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
     "@types/react": "^18.0.0",
     "@types/react-dom": "^17.0.8",
     "@types/warning": "^3.0.0",
-    "@umijs/fabric": "^2.4.8",
+    "@umijs/fabric": "^3.0.0",
     "dumi": "^1.1.4",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",

From 63644b4edd0386cd3d3153837a2998c989dbf47c Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Mon, 5 Sep 2022 15:15:15 +0800
Subject: [PATCH 271/397] type: add id prop (#627)

* type: add id prop

* fix: fallback version

* fix: change order
---
 src/TreeNode.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 82fdb6f4..a320855f 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -17,6 +17,7 @@ export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
   prefixCls?: string;
   className?: string;
   style?: React.CSSProperties;
+  id?: string;
 
   // By parent
   expanded?: boolean;

From bdfe4b24e9f26d235029bf9a4c3a2aab229a5d79 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Tue, 13 Sep 2022 19:46:28 +0800
Subject: [PATCH 272/397] fix: disabled draggable node should have draggable
 className (#632)

* fix: disabled draggable node should have draggable className

* chore: code clean

* chore: code clean

* chore: code clean

* chore: code clean
---
 .fatherrc.js                 |  2 +-
 src/TreeNode.tsx             |  2 +-
 tests/TreeDraggable.spec.tsx | 12 ++++++++++++
 3 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/.fatherrc.js b/.fatherrc.js
index 912aa0aa..f6810f73 100644
--- a/.fatherrc.js
+++ b/.fatherrc.js
@@ -3,7 +3,7 @@ export default {
   esm: { type: 'babel', importLibToEs: true },
   preCommit: {
     eslint: true,
-    prettier: true,
+    prettier: false,
   },
   runtimeHelpers: true,
 };
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index a320855f..c24447ec 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -577,7 +577,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           [`${prefixCls}-treenode-loading`]: loading,
           [`${prefixCls}-treenode-active`]: active,
           [`${prefixCls}-treenode-leaf-last`]: isEndNode,
-          [`${prefixCls}-treenode-draggable`]: draggableWithoutDisabled,
+          [`${prefixCls}-treenode-draggable`]: mergedDraggable,
 
           dragging,
           'drop-target': dropTargetKey === eventKey,
diff --git a/tests/TreeDraggable.spec.tsx b/tests/TreeDraggable.spec.tsx
index 124153a1..21267bd3 100644
--- a/tests/TreeDraggable.spec.tsx
+++ b/tests/TreeDraggable.spec.tsx
@@ -1135,4 +1135,16 @@ describe('Tree Draggable', () => {
     // Should not break
     fireEvent.dragEnd(container.querySelector('.test'));
   });
+
+  it('disabled item should have draggable className', () => {
+    const { container } = render(
+      <Tree draggable defaultExpandAll>
+        <TreeNode title="parent 1" key="0-0">
+          <TreeNode title="leaf" key="0-0-0" disabled />
+          <TreeNode title="leaf" key="0-0-1" />
+        </TreeNode>
+      </Tree>,
+    );
+    expect(container.querySelectorAll('.rc-tree-treenode-draggable').length).toBe(3);
+  });
 });

From 4c3f9628ba1d47105c25e98e58759f9cb856716e Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Tue, 13 Sep 2022 19:55:35 +0800
Subject: [PATCH 273/397] v5.6.7

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 11d8dec5..df3c5ead 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.6",
+  "version": "5.6.7",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From b2d382d4f7236772f584a31279b621beef376ee1 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Tue, 13 Sep 2022 19:56:05 +0800
Subject: [PATCH 274/397] v5.6.8

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index df3c5ead..5cc17bd4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.7",
+  "version": "5.6.8",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 654edb7150ecff27c0659070ec77e7bd7c4f064b Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Tue, 13 Sep 2022 20:21:05 +0800
Subject: [PATCH 275/397] Revert "fix: disabled draggable node should have
 draggable className (#632)"

This reverts commit bdfe4b24e9f26d235029bf9a4c3a2aab229a5d79.
---
 .fatherrc.js                 |  2 +-
 src/TreeNode.tsx             |  2 +-
 tests/TreeDraggable.spec.tsx | 12 ------------
 3 files changed, 2 insertions(+), 14 deletions(-)

diff --git a/.fatherrc.js b/.fatherrc.js
index f6810f73..912aa0aa 100644
--- a/.fatherrc.js
+++ b/.fatherrc.js
@@ -3,7 +3,7 @@ export default {
   esm: { type: 'babel', importLibToEs: true },
   preCommit: {
     eslint: true,
-    prettier: false,
+    prettier: true,
   },
   runtimeHelpers: true,
 };
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index c24447ec..a320855f 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -577,7 +577,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           [`${prefixCls}-treenode-loading`]: loading,
           [`${prefixCls}-treenode-active`]: active,
           [`${prefixCls}-treenode-leaf-last`]: isEndNode,
-          [`${prefixCls}-treenode-draggable`]: mergedDraggable,
+          [`${prefixCls}-treenode-draggable`]: draggableWithoutDisabled,
 
           dragging,
           'drop-target': dropTargetKey === eventKey,
diff --git a/tests/TreeDraggable.spec.tsx b/tests/TreeDraggable.spec.tsx
index 21267bd3..124153a1 100644
--- a/tests/TreeDraggable.spec.tsx
+++ b/tests/TreeDraggable.spec.tsx
@@ -1135,16 +1135,4 @@ describe('Tree Draggable', () => {
     // Should not break
     fireEvent.dragEnd(container.querySelector('.test'));
   });
-
-  it('disabled item should have draggable className', () => {
-    const { container } = render(
-      <Tree draggable defaultExpandAll>
-        <TreeNode title="parent 1" key="0-0">
-          <TreeNode title="leaf" key="0-0-0" disabled />
-          <TreeNode title="leaf" key="0-0-1" />
-        </TreeNode>
-      </Tree>,
-    );
-    expect(container.querySelectorAll('.rc-tree-treenode-draggable').length).toBe(3);
-  });
 });

From a486403787f71a94d8b08f06821a4cf4af9815e2 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Tue, 13 Sep 2022 20:21:53 +0800
Subject: [PATCH 276/397] v5.6.9

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 5cc17bd4..86ede9a8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.8",
+  "version": "5.6.9",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 953d96de6d578503f796e5a75c60b30e9de8bc32 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Tue, 13 Sep 2022 20:23:42 +0800
Subject: [PATCH 277/397] Revert "Revert "fix: disabled draggable node should
 have draggable className (#632)""

This reverts commit 654edb7150ecff27c0659070ec77e7bd7c4f064b.
---
 .fatherrc.js                 |  2 +-
 src/TreeNode.tsx             |  2 +-
 tests/TreeDraggable.spec.tsx | 12 ++++++++++++
 3 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/.fatherrc.js b/.fatherrc.js
index 912aa0aa..f6810f73 100644
--- a/.fatherrc.js
+++ b/.fatherrc.js
@@ -3,7 +3,7 @@ export default {
   esm: { type: 'babel', importLibToEs: true },
   preCommit: {
     eslint: true,
-    prettier: true,
+    prettier: false,
   },
   runtimeHelpers: true,
 };
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index a320855f..c24447ec 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -577,7 +577,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           [`${prefixCls}-treenode-loading`]: loading,
           [`${prefixCls}-treenode-active`]: active,
           [`${prefixCls}-treenode-leaf-last`]: isEndNode,
-          [`${prefixCls}-treenode-draggable`]: draggableWithoutDisabled,
+          [`${prefixCls}-treenode-draggable`]: mergedDraggable,
 
           dragging,
           'drop-target': dropTargetKey === eventKey,
diff --git a/tests/TreeDraggable.spec.tsx b/tests/TreeDraggable.spec.tsx
index 124153a1..21267bd3 100644
--- a/tests/TreeDraggable.spec.tsx
+++ b/tests/TreeDraggable.spec.tsx
@@ -1135,4 +1135,16 @@ describe('Tree Draggable', () => {
     // Should not break
     fireEvent.dragEnd(container.querySelector('.test'));
   });
+
+  it('disabled item should have draggable className', () => {
+    const { container } = render(
+      <Tree draggable defaultExpandAll>
+        <TreeNode title="parent 1" key="0-0">
+          <TreeNode title="leaf" key="0-0-0" disabled />
+          <TreeNode title="leaf" key="0-0-1" />
+        </TreeNode>
+      </Tree>,
+    );
+    expect(container.querySelectorAll('.rc-tree-treenode-draggable').length).toBe(3);
+  });
 });

From 5635072d02fa30382e1657ed8b4da274ed864aa1 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Tue, 13 Sep 2022 20:24:37 +0800
Subject: [PATCH 278/397] v5.7.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 86ede9a8..6ae8116f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.6.9",
+  "version": "5.7.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From e2da1ce6cdf62bb685254865364f3fec4ed56651 Mon Sep 17 00:00:00 2001
From: Jean-Louis Jouannic <862310+jljouannic@users.noreply.github.com>
Date: Mon, 19 Sep 2022 11:30:35 +0200
Subject: [PATCH 279/397] =?UTF-8?q?=F0=9F=90=9B=20adds=20missing=20constra?=
 =?UTF-8?q?int=20for=20TreeDataType=20(#636)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Solves react-component/tree#629
---
 src/NodeList.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 6a8a3a3f..018188f1 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -4,7 +4,7 @@
 
 import * as React from 'react';
 import VirtualList, { ListRef } from 'rc-virtual-list';
-import { FlattenNode, Key, DataEntity, DataNode, ScrollTo } from './interface';
+import { BasicDataNode, FlattenNode, Key, DataEntity, DataNode, ScrollTo } from './interface';
 import MotionTreeNode from './MotionTreeNode';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
 import { getTreeNodeProps, getKey } from './utils/treeUtil';
@@ -54,7 +54,7 @@ export interface NodeListRef {
   getIndentWidth: () => number;
 }
 
-interface NodeListProps<TreeDataType> {
+interface NodeListProps<TreeDataType extends BasicDataNode> {
   prefixCls: string;
   style: React.CSSProperties;
   data: FlattenNode<TreeDataType>[];

From 7dbaebb3fa3ddead8b0a24284169a12133ca074e Mon Sep 17 00:00:00 2001
From: Evgeniya Osmakova <evgeniya.osmakova@gmail.com>
Date: Fri, 18 Nov 2022 09:23:11 +0300
Subject: [PATCH 280/397] fix activeKey type (Key => Key | null) (#664)

---
 src/Tree.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index a050fafe..c51dff53 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -85,7 +85,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   className?: string;
   style?: React.CSSProperties;
   focusable?: boolean;
-  activeKey?: Key;
+  activeKey?: Key | null;
   tabIndex?: number;
   children?: React.ReactNode;
   treeData?: TreeDataType[]; // Generate treeNode by children
@@ -221,7 +221,7 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
   flattenNodes: FlattenNode<TreeDataType>[];
 
   focused: boolean;
-  activeKey: Key;
+  activeKey: Key | null;
 
   // Record if list is changing
   listChanging: boolean;
@@ -234,7 +234,7 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
 class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends React.Component<
   TreeProps<TreeDataType>,
   TreeState<TreeDataType>
-> {
+  > {
   static defaultProps = {
     prefixCls: 'rc-tree',
     showLine: false,
@@ -1171,7 +1171,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
   };
 
   // =========================== Keyboard ===========================
-  onActiveChange = (newActiveKey: Key) => {
+  onActiveChange = (newActiveKey: Key | null) => {
     const { activeKey } = this.state;
     const { onActiveChange } = this.props;
 

From e64a2fa293e9e84068c0ba40cddedfda125b84ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Sat, 19 Nov 2022 12:24:21 +0800
Subject: [PATCH 281/397] chore: fix ts warning (#668)

---
 src/TreeNode.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index c24447ec..c3c76faf 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -512,7 +512,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
         direction,
       },
     } = this.props;
-    const rootDraggable = draggable !== false;
+    const rootDraggable = !!draggable;
     // allowDrop is calculated in Tree.tsx, there is no need for calc it here
     const showIndicator = !disabled && rootDraggable && dragOverNodeKey === eventKey;
     return showIndicator

From 2b53dfe24b425ad106557ebbffbf8f99667dca00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Sat, 19 Nov 2022 12:27:41 +0800
Subject: [PATCH 282/397] 5.7.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6ae8116f..a5851b86 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.0",
+  "version": "5.7.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From e6bca94ef375b94de5fdb770c287408a0b156013 Mon Sep 17 00:00:00 2001
From: cyan <504512494@qq.com>
Date: Sun, 4 Dec 2022 15:37:59 +0800
Subject: [PATCH 283/397] style: set hidden treeNode's height strict to 0
 (#677)

* style: set hidden treeNode's height strict to 0

* update: snapshot
---
 src/NodeList.tsx                              |  2 +
 tests/__snapshots__/Tree.spec.tsx.snap        | 16 +++---
 .../__snapshots__/TreeNodeProps.spec.tsx.snap | 18 +++----
 tests/__snapshots__/TreeProps.spec.tsx.snap   | 50 +++++++++----------
 4 files changed, 44 insertions(+), 42 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 018188f1..525bfd47 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -297,6 +297,8 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
           visibility: 'hidden',
           height: 0,
           overflow: 'hidden',
+          border: 0,
+          padding: 0,
         }}
       >
         <div className={`${prefixCls}-indent`}>
diff --git a/tests/__snapshots__/Tree.spec.tsx.snap b/tests/__snapshots__/Tree.spec.tsx.snap
index 996ebd9e..edf8c435 100644
--- a/tests/__snapshots__/Tree.spec.tsx.snap
+++ b/tests/__snapshots__/Tree.spec.tsx.snap
@@ -16,7 +16,7 @@ exports[`Tree Basic check basic render 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -156,7 +156,7 @@ exports[`Tree Basic check check after data ready works 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -263,7 +263,7 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -337,7 +337,7 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -434,7 +434,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -591,7 +591,7 @@ exports[`Tree Basic renders correctly 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -803,7 +803,7 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -875,7 +875,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
diff --git a/tests/__snapshots__/TreeNodeProps.spec.tsx.snap b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
index 6ba53bca..72492e59 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
@@ -16,7 +16,7 @@ exports[`TreeNode Props className 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -206,7 +206,7 @@ exports[`TreeNode Props customize icon component 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -281,7 +281,7 @@ exports[`TreeNode Props customize icon element 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -356,7 +356,7 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -424,7 +424,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -619,7 +619,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -814,7 +814,7 @@ exports[`TreeNode Props isLeaf 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -909,7 +909,7 @@ exports[`TreeNode Props isLeaf 2`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1010,7 +1010,7 @@ exports[`TreeNode Props isLeaf 3`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
diff --git a/tests/__snapshots__/TreeProps.spec.tsx.snap b/tests/__snapshots__/TreeProps.spec.tsx.snap
index fbd292a6..9ba7de48 100644
--- a/tests/__snapshots__/TreeProps.spec.tsx.snap
+++ b/tests/__snapshots__/TreeProps.spec.tsx.snap
@@ -16,7 +16,7 @@ exports[`Tree Props checkStrictly 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -123,7 +123,7 @@ exports[`Tree Props checkable default 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -230,7 +230,7 @@ exports[`Tree Props checkable without selectable 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -337,7 +337,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -498,7 +498,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -727,7 +727,7 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -772,7 +772,7 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -817,7 +817,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -919,7 +919,7 @@ exports[`Tree Props disabled basic 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -991,7 +991,7 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1062,7 +1062,7 @@ exports[`Tree Props icon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1171,7 +1171,7 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1278,7 +1278,7 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1385,7 +1385,7 @@ exports[`Tree Props loadData basic 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1480,7 +1480,7 @@ exports[`Tree Props multiple 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1581,7 +1581,7 @@ exports[`Tree Props prefixCls 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1625,7 +1625,7 @@ exports[`Tree Props prefixCls 2`] = `
   <div
     aria-hidden="true"
     class="test-prefix-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="test-prefix-indent"
@@ -1669,7 +1669,7 @@ exports[`Tree Props selectable with selectable 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1770,7 +1770,7 @@ exports[`Tree Props selectable without selectable 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1871,7 +1871,7 @@ exports[`Tree Props showIcon 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -1968,7 +1968,7 @@ exports[`Tree Props showIcon 2`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -2059,7 +2059,7 @@ exports[`Tree Props showIcon 3`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -2253,7 +2253,7 @@ exports[`Tree Props showLine 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -2297,7 +2297,7 @@ exports[`Tree Props treeData 1`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"
@@ -2550,7 +2550,7 @@ exports[`Tree Props treeData 2`] = `
   <div
     aria-hidden="true"
     class="rc-tree-treenode"
-    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden;"
+    style="position: absolute; pointer-events: none; visibility: hidden; height: 0px; overflow: hidden; border: 0px; padding: 0px;"
   >
     <div
       class="rc-tree-indent"

From 62e0bf0b91d86b6e42fee69870ada9a4640b6c6f Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Sun, 4 Dec 2022 15:42:41 +0800
Subject: [PATCH 284/397] 5.7.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a5851b86..f2c1d2e6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.1",
+  "version": "5.7.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 7634a74f830e53f159c9a9f93108d04a6f78e026 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Mon, 20 Mar 2023 10:26:08 +0800
Subject: [PATCH 285/397] chore: fix React 18.3 warning (#732)

---
 package.json           |  4 ++--
 src/TreeNode.tsx       |  8 ++++----
 tests/React18.spec.tsx | 22 +++++++++++++++++++++-
 tests/util.spec.js     | 11 +++++------
 4 files changed, 32 insertions(+), 13 deletions(-)

diff --git a/package.json b/package.json
index f2c1d2e6..5f07c8d0 100644
--- a/package.json
+++ b/package.json
@@ -70,8 +70,8 @@
     "rc-dialog": "^8.1.0",
     "rc-tooltip": "5.x",
     "rc-trigger": "^5.0.7",
-    "react": "^18.0.0",
-    "react-dom": "^18.0.0",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
     "typescript": "^4.0.2"
   },
   "dependencies": {
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index c3c76faf..d8b5129e 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -438,7 +438,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
   // Icon + Title
   renderSelector = () => {
     const { dragNodeHighlight } = this.state;
-    const { title, selected, icon, loading, data } = this.props;
+    const { title = defaultTitle, selected, icon, loading, data } = this.props;
     const {
       context: { prefixCls, showIcon, icon: treeIcon, loadData, titleRender },
     } = this.props;
@@ -620,9 +620,9 @@ const ContextTreeNode: React.FC<TreeNodeProps> = props => (
 
 ContextTreeNode.displayName = 'TreeNode';
 
-ContextTreeNode.defaultProps = {
-  title: defaultTitle,
-};
+// ContextTreeNode.defaultProps = {
+//   title: defaultTitle,
+// };
 
 (ContextTreeNode as any).isTreeNode = 1;
 
diff --git a/tests/React18.spec.tsx b/tests/React18.spec.tsx
index 0e945d9d..2db0146d 100644
--- a/tests/React18.spec.tsx
+++ b/tests/React18.spec.tsx
@@ -1,8 +1,28 @@
+import { fireEvent, render } from '@testing-library/react';
 import React from 'react';
-import { render, fireEvent } from '@testing-library/react';
 import Tree from '../src';
 
 describe('React 18', () => {
+  // This does not real work since waring is in 18.3.0 but current still is next version.
+  it('no warning', () => {
+    const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+    render(
+      <React.StrictMode>
+        <Tree
+          defaultExpandAll
+          treeData={[
+            {
+              title: 'Parent',
+              key: 'parent',
+            },
+          ]}
+        />
+      </React.StrictMode>,
+    );
+
+    expect(errSpy).not.toHaveBeenCalled();
+  });
+
   it('expand work', () => {
     const onExpand = jest.fn();
     const { container } = render(
diff --git a/tests/util.spec.js b/tests/util.spec.js
index 072bd965..a0b73530 100644
--- a/tests/util.spec.js
+++ b/tests/util.spec.js
@@ -1,22 +1,21 @@
 /* eslint-disable no-undef, react/no-multi-comp,
 react/no-unused-state, react/prop-types, no-return-assign */
-import React from 'react';
 import Tree, { TreeNode } from '../src';
 import {
-  convertDataToTree,
   conductExpandParent,
+  convertDataToTree,
   getDragChildrenKeys,
   parseCheckedKeys,
 } from '../src/util';
+import { conductCheck } from '../src/utils/conductUtil';
 import {
-  flattenTreeData,
-  convertTreeToData,
   convertDataToEntities,
+  convertTreeToData,
+  flattenTreeData,
   getTreeNodeProps,
   traverseDataNodes,
 } from '../src/utils/treeUtil';
 import { spyConsole, spyError } from './util';
-import { conductCheck } from '../src/utils/conductUtil';
 
 describe('Util', () => {
   spyConsole();
@@ -58,7 +57,7 @@ describe('Util', () => {
           },
         ],
       },
-      { key: '!', title: '---', really: true },
+      { key: '!', really: true },
     ]);
   });
 

From d8b6e2d876526c41977241805cd9a4c49fb2a12d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Mon, 20 Mar 2023 10:34:05 +0800
Subject: [PATCH 286/397] chore: clean up (#733)

---
 src/TreeNode.tsx | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index d8b5129e..a32b12b4 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -620,10 +620,6 @@ const ContextTreeNode: React.FC<TreeNodeProps> = props => (
 
 ContextTreeNode.displayName = 'TreeNode';
 
-// ContextTreeNode.defaultProps = {
-//   title: defaultTitle,
-// };
-
 (ContextTreeNode as any).isTreeNode = 1;
 
 export { InternalTreeNode };

From 4c9cc52a62f7187b8507d0eadee2a47d571832eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 20 Mar 2023 10:36:48 +0800
Subject: [PATCH 287/397] 5.7.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 5f07c8d0..bd7c112b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.2",
+  "version": "5.7.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 660e325c51d973c405b70555cdc516b8f5dc53ee Mon Sep 17 00:00:00 2001
From: acyza <101238421+acyza@users.noreply.github.com>
Date: Thu, 30 Mar 2023 23:32:13 +0800
Subject: [PATCH 288/397] fix: invalid animation (#735)

* fix: invalid animation

* chore: fix test

* chore: fix test
---
 src/MotionTreeNode.tsx    | 10 ++++++++--
 tests/TreeMotion.spec.tsx |  6 ++++++
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index d8cb6717..0fd4b378 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -51,15 +51,21 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
     }
   }, [motionNodes]);
 
+  let reruningEffectFlag = null;
+
   useEffect(() => {
     // Trigger motion only when patched
     if (motionNodes) {
-      onOriginMotionStart();
+      if (reruningEffectFlag === null) {
+        onOriginMotionStart();
+      } else {
+        clearTimeout(reruningEffectFlag);
+      }
     }
 
     return () => {
       if (motionNodes) {
-        onMotionEnd();
+        reruningEffectFlag = setTimeout(onMotionEnd, 0);
       }
     };
   }, []);
diff --git a/tests/TreeMotion.spec.tsx b/tests/TreeMotion.spec.tsx
index 187bc198..496f1fb4 100644
--- a/tests/TreeMotion.spec.tsx
+++ b/tests/TreeMotion.spec.tsx
@@ -137,6 +137,9 @@ describe('Tree Motion', () => {
       expect(onMotionEnd).not.toHaveBeenCalled();
 
       unmount();
+      act(() => {
+        jest.runAllTimers();
+      });
       expect(onMotionEnd).toHaveBeenCalled();
     });
 
@@ -166,6 +169,9 @@ describe('Tree Motion', () => {
       expect(onMotionEnd).not.toHaveBeenCalled();
 
       unmount();
+      act(() => {
+        jest.runAllTimers();
+      });
       expect(onMotionStart).not.toHaveBeenCalled();
       expect(onMotionEnd).not.toHaveBeenCalled();
     });

From a72fa8409586b1cba4e41f0fad2ca1ec549ace42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=B4=8B?= <94534613+BoyYangzai@users.noreply.github.com>
Date: Wed, 17 May 2023 21:27:41 +0800
Subject: [PATCH 289/397] chore: update virtual-list 5.3.1 (#744)

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index bd7c112b..462c18a9 100644
--- a/package.json
+++ b/package.json
@@ -79,6 +79,6 @@
     "classnames": "2.x",
     "rc-motion": "^2.0.1",
     "rc-util": "^5.16.1",
-    "rc-virtual-list": "^3.4.8"
+    "rc-virtual-list": "^3.5.1"
   }
 }

From c69f16e2d1e690d26bfb62c72f328c0cec3f87cb Mon Sep 17 00:00:00 2001
From: yangxiuxiu <79584569+RicardoErii@users.noreply.github.com>
Date: Wed, 24 May 2023 15:59:46 +0800
Subject: [PATCH 290/397] fix: TreeNode false flicker in re-render (#743)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* fix: TreeNode false flicker in re-render

* chore: adjust using rc-util

---------

Co-authored-by: RicardoErii <‘1974364190@qq.com’>
Co-authored-by: 二货机器人 <smith3816@gmail.com>
---
 src/NodeList.tsx | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 525bfd47..6bf73eb0 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -2,12 +2,13 @@
  * Handle virtual list of the TreeNodes.
  */
 
-import * as React from 'react';
+import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
 import VirtualList, { ListRef } from 'rc-virtual-list';
-import { BasicDataNode, FlattenNode, Key, DataEntity, DataNode, ScrollTo } from './interface';
+import * as React from 'react';
+import { BasicDataNode, DataEntity, DataNode, FlattenNode, Key, ScrollTo } from './interface';
 import MotionTreeNode from './MotionTreeNode';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
-import { getTreeNodeProps, getKey } from './utils/treeUtil';
+import { getKey, getTreeNodeProps } from './utils/treeUtil';
 
 const HIDDEN_STYLE = {
   width: 0,
@@ -199,7 +200,8 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
   }
 
   // Do animation if expanded keys changed
-  React.useEffect(() => {
+  // layoutEffect here to avoid blink of node removing
+  useLayoutEffect(() => {
     setPrevExpandedKeys(expandedKeys);
 
     const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys);

From 33ccbeaca39f1a34c4a79597a7cd9ea52b4086ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Wed, 24 May 2023 16:10:30 +0800
Subject: [PATCH 291/397] 5.7.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 462c18a9..85d6f377 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.3",
+  "version": "5.7.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 57511c8e8771cff71d47382362a4807487ba111f Mon Sep 17 00:00:00 2001
From: muxin <a2944938071@163.com>
Date: Wed, 14 Jun 2023 10:43:36 +0800
Subject: [PATCH 292/397] refactor: solve circule dependency (#749)

* refactor: solve circule dependency

* fix: add extra export path
---
 src/MotionTreeNode.tsx |  4 ++--
 src/TreeNode.tsx       | 40 ++--------------------------------------
 src/index.ts           |  3 +--
 src/interface.tsx      | 38 +++++++++++++++++++++++++++++++++++++-
 src/util.tsx           | 10 ++--------
 src/utils/treeUtil.ts  | 27 +++++++++++++++++----------
 6 files changed, 61 insertions(+), 61 deletions(-)

diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 0fd4b378..60d17169 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -2,8 +2,8 @@ import * as React from 'react';
 import { useEffect } from 'react';
 import classNames from 'classnames';
 import CSSMotion from 'rc-motion';
-import TreeNode, { TreeNodeProps } from './TreeNode';
-import { FlattenNode } from './interface';
+import TreeNode from './TreeNode';
+import { FlattenNode, TreeNodeProps } from './interface';
 import { getTreeNodeProps, TreeNodeRequiredProps } from './utils/treeUtil';
 import { TreeContext } from './contextTypes';
 
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index a32b12b4..dc4a319b 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -3,7 +3,7 @@ import classNames from 'classnames';
 import pickAttrs from 'rc-util/lib/pickAttrs';
 // @ts-ignore
 import { TreeContext, TreeContextProps } from './contextTypes';
-import { IconType, Key, DataNode, BasicDataNode } from './interface';
+import { TreeNodeProps } from './interface';
 import Indent from './Indent';
 import { convertNodePropsToEventData } from './utils/treeUtil';
 
@@ -12,43 +12,7 @@ const ICON_CLOSE = 'close';
 
 const defaultTitle = '---';
 
-export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
-  eventKey?: Key; // Pass by parent `cloneElement`
-  prefixCls?: string;
-  className?: string;
-  style?: React.CSSProperties;
-  id?: string;
-
-  // By parent
-  expanded?: boolean;
-  selected?: boolean;
-  checked?: boolean;
-  loaded?: boolean;
-  loading?: boolean;
-  halfChecked?: boolean;
-  title?: React.ReactNode | ((data: TreeDataType) => React.ReactNode);
-  dragOver?: boolean;
-  dragOverGapTop?: boolean;
-  dragOverGapBottom?: boolean;
-  pos?: string;
-  domRef?: React.Ref<HTMLDivElement>;
-  /** New added in Tree for easy data access */
-  data?: TreeDataType;
-  isStart?: boolean[];
-  isEnd?: boolean[];
-  active?: boolean;
-  onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
-
-  // By user
-  isLeaf?: boolean;
-  checkable?: boolean;
-  selectable?: boolean;
-  disabled?: boolean;
-  disableCheckbox?: boolean;
-  icon?: IconType;
-  switcherIcon?: IconType;
-  children?: React.ReactNode;
-}
+export type { TreeNodeProps } from './interface';
 
 export interface InternalTreeNodeProps extends TreeNodeProps {
   context?: TreeContextProps;
diff --git a/src/index.ts b/src/index.ts
index e3ad61ab..57929b51 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,8 +1,7 @@
 import Tree from './Tree';
 import TreeNode from './TreeNode';
 import type { TreeProps } from './Tree';
-import type { TreeNodeProps } from './TreeNode';
-import type { BasicDataNode, FieldDataNode } from './interface';
+import type { TreeNodeProps, BasicDataNode, FieldDataNode } from './interface';
 
 export { TreeNode };
 export type { TreeProps, TreeNodeProps, BasicDataNode, FieldDataNode };
diff --git a/src/interface.tsx b/src/interface.tsx
index bf45a272..586cac3c 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -1,7 +1,43 @@
 import * as React from 'react';
-import { TreeNodeProps } from './TreeNode';
 
 export { ScrollTo } from 'rc-virtual-list/lib/List';
+export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
+  eventKey?: Key; // Pass by parent `cloneElement`
+  prefixCls?: string;
+  className?: string;
+  style?: React.CSSProperties;
+  id?: string;
+
+  // By parent
+  expanded?: boolean;
+  selected?: boolean;
+  checked?: boolean;
+  loaded?: boolean;
+  loading?: boolean;
+  halfChecked?: boolean;
+  title?: React.ReactNode | ((data: TreeDataType) => React.ReactNode);
+  dragOver?: boolean;
+  dragOverGapTop?: boolean;
+  dragOverGapBottom?: boolean;
+  pos?: string;
+  domRef?: React.Ref<HTMLDivElement>;
+  /** New added in Tree for easy data access */
+  data?: TreeDataType;
+  isStart?: boolean[];
+  isEnd?: boolean[];
+  active?: boolean;
+  onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
+
+  // By user
+  isLeaf?: boolean;
+  checkable?: boolean;
+  selectable?: boolean;
+  disabled?: boolean;
+  disableCheckbox?: boolean;
+  icon?: IconType;
+  switcherIcon?: IconType;
+  children?: React.ReactNode;
+}
 
 /** For fieldNames, we provides a abstract interface */
 export interface BasicDataNode {
diff --git a/src/util.tsx b/src/util.tsx
index 6165bf46..6b8b1ef2 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -18,6 +18,8 @@ import {
 } from './interface';
 import { TreeProps, AllowDrop } from './Tree';
 
+export { getPosition, isTreeNode } from './utils/treeUtil';
+
 export function arrDel(list: Key[], value: Key) {
   if (!list) return [];
   const clone = list.slice();
@@ -40,14 +42,6 @@ export function posToArr(pos: string) {
   return pos.split('-');
 }
 
-export function getPosition(level: string | number, index: number) {
-  return `${level}-${index}`;
-}
-
-export function isTreeNode(node: NodeElement) {
-  return node && node.type && node.type.isTreeNode;
-}
-
 export function getDragChildrenKeys<TreeDataType extends BasicDataNode = DataNode>(
   dragNodeKey: Key,
   keyEntities: Record<Key, DataEntity<TreeDataType>>,
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 50a3f637..5ea0b6cf 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -1,20 +1,27 @@
-import * as React from 'react';
-import omit from 'rc-util/lib/omit';
 import toArray from 'rc-util/lib/Children/toArray';
+import omit from 'rc-util/lib/omit';
 import warning from 'rc-util/lib/warning';
+import * as React from 'react';
 import {
-  DataNode,
-  FlattenNode,
-  NodeElement,
+  BasicDataNode,
   DataEntity,
-  Key,
+  DataNode,
   EventDataNode,
-  GetKey,
   FieldNames,
-  BasicDataNode,
+  FlattenNode,
+  GetKey,
+  Key,
+  NodeElement,
+  TreeNodeProps,
 } from '../interface';
-import { getPosition, isTreeNode } from '../util';
-import { TreeNodeProps } from '../TreeNode';
+
+export function getPosition(level: string | number, index: number) {
+  return `${level}-${index}`;
+}
+
+export function isTreeNode(node: NodeElement) {
+  return node && node.type && node.type.isTreeNode;
+}
 
 export function getKey(key: Key, pos: string) {
   if (key !== null && key !== undefined) {

From bf421a3e7d03d413ed8d1b69c56346a503366c0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Wed, 14 Jun 2023 10:47:22 +0800
Subject: [PATCH 293/397] 5.7.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 85d6f377..4c3eeda4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.4",
+  "version": "5.7.5",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From ed153ad66f612079f1e79ede11d36985c03b6468 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Fri, 16 Jun 2023 11:21:48 +0800
Subject: [PATCH 294/397] fix: disabled motion will never end (#754)

* refactor: use rc-util hooks

* fix: motion of rc-motion

* chore: patch onMotion

* fix: adjust logic
---
 docs/examples/animation.jsx | 76 ++++++++++++++++++++++---------------
 src/MotionTreeNode.tsx      | 63 +++++++++++++++---------------
 src/useUnmount.ts           | 21 ++++++++++
 tests/TreeMotion.spec.tsx   | 30 +++++++++------
 4 files changed, 115 insertions(+), 75 deletions(-)
 create mode 100644 src/useUnmount.ts

diff --git a/docs/examples/animation.jsx b/docs/examples/animation.jsx
index 703d491b..82d345a0 100644
--- a/docs/examples/animation.jsx
+++ b/docs/examples/animation.jsx
@@ -1,8 +1,9 @@
 /* eslint no-console:0, react/no-danger: 0 */
+import { Provider } from 'rc-motion';
+import Tree from 'rc-tree';
+import React from 'react';
 import '../../assets/index.less';
 import './animation.less';
-import React from 'react';
-import Tree from 'rc-tree';
 
 const STYLE = `
 .rc-tree-child-tree {
@@ -108,43 +109,56 @@ function getTreeData() {
 
 const Demo = () => {
   const treeRef = React.useRef();
+  const [enableMotion, setEnableMotion] = React.useState(true);
 
   setTimeout(() => {
     treeRef.current.scrollTo({ key: '0-9-2' });
   }, 100);
 
   return (
-    <div className="animation">
-      <h2>expanded</h2>
-      <style dangerouslySetInnerHTML={{ __html: STYLE }} />
+    <Provider motion={enableMotion}>
+      <button
+        onClick={() => {
+          setEnableMotion(e => !e);
+        }}
+      >
+        Motion: {String(enableMotion)}
+      </button>
 
-      <div style={{ display: 'flex' }}>
-        <div style={{ flex: '1 1 50%' }}>
-          <h3>With Virtual</h3>
-          <Tree
-            ref={treeRef}
-            // defaultExpandAll={false}
-            defaultExpandAll
-            defaultExpandedKeys={defaultExpandedKeys}
-            motion={motion}
-            height={200}
-            itemHeight={20}
-            style={{ border: '1px solid #000' }}
-            treeData={getTreeData()}
-          />
-        </div>
-        <div style={{ flex: '1 1 50%' }}>
-          <h3>Without Virtual</h3>
-          <Tree
-            defaultExpandAll={false}
-            defaultExpandedKeys={defaultExpandedKeys}
-            motion={motion}
-            style={{ border: '1px solid #000' }}
-            treeData={getTreeData()}
-          />
+      <React.StrictMode>
+        <div className="animation">
+          <h2>expanded</h2>
+          <style dangerouslySetInnerHTML={{ __html: STYLE }} />
+
+          <div style={{ display: 'flex' }}>
+            <div style={{ flex: '1 1 50%' }}>
+              <h3>With Virtual</h3>
+              <Tree
+                ref={treeRef}
+                // defaultExpandAll={false}
+                defaultExpandAll
+                defaultExpandedKeys={defaultExpandedKeys}
+                motion={motion}
+                height={200}
+                itemHeight={20}
+                style={{ border: '1px solid #000' }}
+                treeData={getTreeData()}
+              />
+            </div>
+            <div style={{ flex: '1 1 50%' }}>
+              <h3>Without Virtual</h3>
+              <Tree
+                defaultExpandAll={false}
+                defaultExpandedKeys={defaultExpandedKeys}
+                motion={motion}
+                style={{ border: '1px solid #000' }}
+                treeData={getTreeData()}
+              />
+            </div>
+          </div>
         </div>
-      </div>
-    </div>
+      </React.StrictMode>
+    </Provider>
   );
 };
 
diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 60d17169..9e50adb5 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -1,11 +1,12 @@
-import * as React from 'react';
-import { useEffect } from 'react';
 import classNames from 'classnames';
 import CSSMotion from 'rc-motion';
-import TreeNode from './TreeNode';
+import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
+import * as React from 'react';
+import { TreeContext } from './contextTypes';
 import { FlattenNode, TreeNodeProps } from './interface';
+import TreeNode from './TreeNode';
+import useUnmount from './useUnmount';
 import { getTreeNodeProps, TreeNodeRequiredProps } from './utils/treeUtil';
-import { TreeContext } from './contextTypes';
 
 interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
   active: boolean;
@@ -36,39 +37,38 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
   const [visible, setVisible] = React.useState(true);
   const { prefixCls } = React.useContext(TreeContext);
 
-  const motionedRef = React.useRef(false);
+  // Calculate target visible here.
+  // And apply in effect to make `leave` motion work.
+  const targetVisible = motionNodes && motionType !== 'hide';
 
-  const onMotionEnd = () => {
-    if (!motionedRef.current) {
-      onOriginMotionEnd();
-    }
-    motionedRef.current = true;
-  };
+  useLayoutEffect(() => {
+    if (motionNodes) {
+      onOriginMotionStart();
 
-  useEffect(() => {
-    if (motionNodes && motionType === 'hide' && visible) {
-      setVisible(false);
+      if (targetVisible !== visible) {
+        setVisible(targetVisible);
+      }
     }
   }, [motionNodes]);
 
-  let reruningEffectFlag = null;
-
-  useEffect(() => {
-    // Trigger motion only when patched
-    if (motionNodes) {
-      if (reruningEffectFlag === null) {
-        onOriginMotionStart();
-      } else {
-        clearTimeout(reruningEffectFlag);
-      }
+  // Should only trigger once
+  const triggerMotionEndRef = React.useRef(false);
+  const triggerMotionEnd = () => {
+    if (motionNodes && !triggerMotionEndRef.current) {
+      triggerMotionEndRef.current = true;
+      onOriginMotionEnd();
     }
+  };
 
-    return () => {
-      if (motionNodes) {
-        reruningEffectFlag = setTimeout(onMotionEnd, 0);
-      }
-    };
-  }, []);
+  // Effect if unmount
+  useUnmount(triggerMotionEnd);
+
+  // Motion end event
+  const onVisibleChanged = (nextVisible: boolean) => {
+    if (targetVisible === nextVisible) {
+      triggerMotionEnd();
+    }
+  };
 
   if (motionNodes) {
     return (
@@ -77,8 +77,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
         visible={visible}
         {...motion}
         motionAppear={motionType === 'show'}
-        onAppearEnd={onMotionEnd}
-        onLeaveEnd={onMotionEnd}
+        onVisibleChanged={onVisibleChanged}
       >
         {({ className: motionClassName, style: motionStyle }, motionRef) => (
           <div
diff --git a/src/useUnmount.ts b/src/useUnmount.ts
new file mode 100644
index 00000000..50b2e3aa
--- /dev/null
+++ b/src/useUnmount.ts
@@ -0,0 +1,21 @@
+import * as React from 'react';
+
+/**
+ * Trigger only when component unmount
+ */
+export default function useUnmount(callback: VoidFunction) {
+  const [enabled, setEnabled] = React.useState(false);
+
+  React.useLayoutEffect(
+    () => () => {
+      if (enabled) {
+        callback();
+      }
+    },
+    [enabled],
+  );
+
+  React.useLayoutEffect(() => {
+    setEnabled(true);
+  }, []);
+}
diff --git a/tests/TreeMotion.spec.tsx b/tests/TreeMotion.spec.tsx
index 496f1fb4..bfd491a4 100644
--- a/tests/TreeMotion.spec.tsx
+++ b/tests/TreeMotion.spec.tsx
@@ -1,8 +1,8 @@
+import { act, fireEvent, render } from '@testing-library/react';
 import React from 'react';
-import { render, fireEvent, act } from '@testing-library/react';
-import Tree, { TreeNode, FieldDataNode } from '../src';
-import MotionTreeNode from '../src/MotionTreeNode';
+import Tree, { FieldDataNode, TreeNode } from '../src';
 import { TreeContext } from '../src/contextTypes';
+import MotionTreeNode from '../src/MotionTreeNode';
 import { getMinimumRangeTransitionRange } from '../src/NodeList';
 
 jest.mock('rc-motion/lib/util/motion', () => {
@@ -123,14 +123,20 @@ describe('Tree Motion', () => {
       const onMotionStart = jest.fn();
       const onMotionEnd = jest.fn();
       const { unmount } = render(
-        <TreeContext.Provider value={{ prefixCls: 'test' } as any}>
-          <MotionTreeNode
-            motionNodes={[]}
-            onMotionStart={onMotionStart}
-            onMotionEnd={onMotionEnd}
-            {...({} as any)} // Ignore TS warning
-          />
-        </TreeContext.Provider>,
+        <React.StrictMode>
+          <TreeContext.Provider value={{ prefixCls: 'test' } as any}>
+            <MotionTreeNode
+              motionNodes={[]}
+              onMotionStart={onMotionStart}
+              onMotionEnd={onMotionEnd}
+              motion={{
+                motionName: 'bamboo',
+              }}
+              motionType="hide"
+              {...({} as any)} // Ignore TS warning
+            />
+          </TreeContext.Provider>
+        </React.StrictMode>,
       );
 
       expect(onMotionStart).toHaveBeenCalled();
@@ -207,7 +213,7 @@ describe('Tree Motion', () => {
 
     const { container } = render(<Demo />);
     expect(container.querySelector('[title="B"]')).toBeTruthy();
-    // wrapper.find('.rc-tree-switcher').first().simulate('click');
+
     fireEvent.click(container.querySelector('.rc-tree-switcher'));
     act(() => {
       jest.runAllTimers();

From 8cab958ae5a6a118edd5a6064ca9029e50023584 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Fri, 16 Jun 2023 11:25:15 +0800
Subject: [PATCH 295/397] 5.7.6

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4c3eeda4..1214ed44 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.5",
+  "version": "5.7.6",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From d155bb12851b7d06179a0a742925f5d6371e54b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Fri, 30 Jun 2023 10:14:41 +0800
Subject: [PATCH 296/397] fix: merge onMotionLogic into one set (#755)

* fix: merge onMotionLogic into one set

* test: add test case

* test: fix test case
---
 src/MotionTreeNode.tsx    | 10 ++++++---
 src/useUnmount.ts         | 27 +++++++++++++----------
 tests/TreeMotion.spec.tsx | 45 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 68 insertions(+), 14 deletions(-)

diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 9e50adb5..b8faad0a 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -43,14 +43,18 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
 
   useLayoutEffect(() => {
     if (motionNodes) {
-      onOriginMotionStart();
-
       if (targetVisible !== visible) {
         setVisible(targetVisible);
       }
     }
   }, [motionNodes]);
 
+  const triggerMotionStart = () => {
+    if (motionNodes) {
+      onOriginMotionStart();
+    }
+  };
+
   // Should only trigger once
   const triggerMotionEndRef = React.useRef(false);
   const triggerMotionEnd = () => {
@@ -61,7 +65,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
   };
 
   // Effect if unmount
-  useUnmount(triggerMotionEnd);
+  useUnmount(triggerMotionStart, triggerMotionEnd);
 
   // Motion end event
   const onVisibleChanged = (nextVisible: boolean) => {
diff --git a/src/useUnmount.ts b/src/useUnmount.ts
index 50b2e3aa..a9492190 100644
--- a/src/useUnmount.ts
+++ b/src/useUnmount.ts
@@ -3,19 +3,24 @@ import * as React from 'react';
 /**
  * Trigger only when component unmount
  */
-export default function useUnmount(callback: VoidFunction) {
-  const [enabled, setEnabled] = React.useState(false);
+export default function useUnmount(triggerStart: VoidFunction, triggerEnd: VoidFunction) {
+  const [firstMount, setFirstMount] = React.useState(false);
 
-  React.useLayoutEffect(
-    () => () => {
-      if (enabled) {
-        callback();
-      }
-    },
-    [enabled],
-  );
+  React.useLayoutEffect(() => {
+    if (firstMount) {
+      triggerStart();
+
+      return () => {
+        triggerEnd();
+      };
+    }
+  }, [firstMount]);
 
   React.useLayoutEffect(() => {
-    setEnabled(true);
+    setFirstMount(true);
+
+    return () => {
+      setFirstMount(false);
+    };
   }, []);
 }
diff --git a/tests/TreeMotion.spec.tsx b/tests/TreeMotion.spec.tsx
index bfd491a4..487f34e2 100644
--- a/tests/TreeMotion.spec.tsx
+++ b/tests/TreeMotion.spec.tsx
@@ -280,4 +280,49 @@ describe('Tree Motion', () => {
     expect(container.querySelectorAll('span.rc-tree-title')[0].textContent).toEqual('parent2');
     expect(container.querySelectorAll('span.rc-tree-title')[1].textContent).toEqual('child2');
   });
+
+  // https://github.com/ant-design/ant-design/issues/43282
+  it('dynamic modify data should stop motion', () => {
+    const motion = {
+      motionName: 'bamboo',
+    };
+
+    const getTreeData = () => [
+      {
+        title: 'parent',
+        key: 'parent',
+        children: [
+          {
+            title: 'child',
+            key: 'child',
+          },
+        ],
+      },
+    ];
+
+    const oriData = getTreeData();
+
+    const { container, rerender } = render(<Tree motion={motion} treeData={oriData} />);
+    rerender(<Tree motion={motion} treeData={oriData} expandedKeys={['parent']} />);
+
+    // Replace `treeData` before motion end
+    const onExpand = jest.fn();
+    rerender(
+      <Tree
+        onExpand={onExpand}
+        motion={motion}
+        treeData={getTreeData()}
+        expandedKeys={['parent']}
+      />,
+    );
+
+    // Delay for clean up
+    act(() => {
+      jest.advanceTimersByTime(100);
+    });
+
+    // Click should trigger event
+    fireEvent.click(container.querySelector('.rc-tree-switcher_open'));
+    expect(onExpand).toHaveBeenCalled();
+  });
 });

From 48d732bff2966c25659b9cdd38f08ad5cf4137f8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Fri, 30 Jun 2023 10:18:14 +0800
Subject: [PATCH 297/397] 5.7.7

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1214ed44..526162a1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.6",
+  "version": "5.7.7",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From e3a8986f0812c56216666bead9343f8c44020e71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Fri, 30 Jun 2023 10:23:53 +0800
Subject: [PATCH 298/397] 5.7.8

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 526162a1..b6c8f447 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.7",
+  "version": "5.7.8",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From ea8ad56b1d9f09e00bc6cc0b6cd334becdd68106 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Thu, 13 Jul 2023 15:47:16 +0800
Subject: [PATCH 299/397] fix: node flink (#756)

---
 src/TreeNode.tsx | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index dc4a319b..07fffef2 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -1,10 +1,10 @@
-import * as React from 'react';
 import classNames from 'classnames';
 import pickAttrs from 'rc-util/lib/pickAttrs';
+import * as React from 'react';
 // @ts-ignore
 import { TreeContext, TreeContextProps } from './contextTypes';
-import { TreeNodeProps } from './interface';
 import Indent from './Indent';
+import { TreeNodeProps } from './interface';
 import { convertNodePropsToEventData } from './utils/treeUtil';
 
 const ICON_OPEN = 'open';
@@ -29,6 +29,8 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
 
   public selectHandle: HTMLSpanElement;
 
+  cacheIndent: number;
+
   // Isomorphic needn't load data in server side
   componentDidMount() {
     this.syncLoadData(this.props);
@@ -479,8 +481,22 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const rootDraggable = !!draggable;
     // allowDrop is calculated in Tree.tsx, there is no need for calc it here
     const showIndicator = !disabled && rootDraggable && dragOverNodeKey === eventKey;
+
+    // This is a hot fix which is already fixed in
+    // https://github.com/react-component/tree/pull/743/files
+    // But some case need break point so we hack on this
+    // ref https://github.com/ant-design/ant-design/issues/43493
+    const mergedIndent = indent ?? this.cacheIndent;
+    this.cacheIndent = indent;
+
     return showIndicator
-      ? dropIndicatorRender({ dropPosition, dropLevelOffset, indent, prefixCls, direction })
+      ? dropIndicatorRender({
+          dropPosition,
+          dropLevelOffset,
+          indent: mergedIndent,
+          prefixCls,
+          direction,
+        })
       : null;
   };
 
@@ -586,6 +602,4 @@ ContextTreeNode.displayName = 'TreeNode';
 
 (ContextTreeNode as any).isTreeNode = 1;
 
-export { InternalTreeNode };
-
 export default ContextTreeNode;

From fb45307802fe85aa10e6af1191e3867bb5b6d714 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Thu, 13 Jul 2023 15:49:31 +0800
Subject: [PATCH 300/397] 5.7.9

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index b6c8f447..64de19c6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.8",
+  "version": "5.7.9",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 93c7908b6f408fb0415ec0dbac45ad61f0efdb36 Mon Sep 17 00:00:00 2001
From: tubs-coder <119929626+tubs-coder@users.noreply.github.com>
Date: Tue, 15 Aug 2023 16:27:03 +1000
Subject: [PATCH 301/397] fix: Access CheckInfo interface for type checking
 when declaring onCheck handler (#757)

When implementing the onCheck handler, there are no available type definitions for the parameters.
---
 src/Tree.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index c51dff53..0dee8cdf 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -53,7 +53,7 @@ import DropIndicator from './DropIndicator';
 
 const MAX_RETRY_TIMES = 10;
 
-interface CheckInfo<TreeDataType extends BasicDataNode = DataNode> {
+export interface CheckInfo<TreeDataType extends BasicDataNode = DataNode> {
   event: 'check';
   node: EventDataNode<TreeDataType>;
   checked: boolean;

From 0fdb4f0feab7efd51bc000b61c1bb71769bfbcf7 Mon Sep 17 00:00:00 2001
From: JiaQi <112228030+Yuiai01@users.noreply.github.com>
Date: Thu, 24 Aug 2023 22:39:55 +0800
Subject: [PATCH 302/397] fix: delete title Click preventDefault in the event
 (#758)

* chore: delete title Click preventDefault in the event

* chore: add test case

---------

Co-authored-by: dujiaqi <dujiaqi@kezaihui.com>
---
 src/TreeNode.tsx             |  2 --
 tests/TreeNodeProps.spec.tsx | 25 +++++++++++++++++++++++++
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 07fffef2..953f4ec0 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -67,7 +67,6 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const {
       context: { onNodeSelect },
     } = this.props;
-    e.preventDefault();
     onNodeSelect(e, convertNodePropsToEventData(this.props));
   };
 
@@ -81,7 +80,6 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
 
     if (!this.isCheckable() || disableCheckbox) return;
 
-    e.preventDefault();
     const targetChecked = !checked;
     onNodeCheck(e, convertNodePropsToEventData(this.props), targetChecked);
   };
diff --git a/tests/TreeNodeProps.spec.tsx b/tests/TreeNodeProps.spec.tsx
index b51a6954..0ecac5b2 100644
--- a/tests/TreeNodeProps.spec.tsx
+++ b/tests/TreeNodeProps.spec.tsx
@@ -214,4 +214,29 @@ describe('TreeNode Props', () => {
     expect(onClick).toHaveBeenCalled();
     expect(onSelect).not.toHaveBeenCalled();
   });
+
+  it('elements within the title should be able to respond to clicks', () => {
+    const onClick = jest.fn();
+
+    const Demo: React.FC<{ filed: string; checkable: boolean }> = ({ filed, checkable }) => (
+      <Tree>
+        <TreeNode
+          checkable={checkable}
+          title={
+            <label className={filed}>
+              <input type="radio" onClick={onClick} />
+            </label>
+          }
+        />
+      </Tree>
+    );
+
+    render(<Demo filed="test-check" checkable={true} />);
+    render(<Demo filed="test-click" checkable={false} />);
+    
+    fireEvent.click(document.querySelector('.test-check'));
+    fireEvent.click(document.querySelector('.test-click'));
+
+    expect(onClick).toHaveBeenCalledTimes(2);
+  });
 });

From b81c3dd4aa48712afbdfa995960af45c6025a5aa Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Fri, 25 Aug 2023 11:01:08 +0800
Subject: [PATCH 303/397] v5.7.10

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 64de19c6..6e8914d1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.9",
+  "version": "5.7.10",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From d10ae86288ebe06f02787ca18d4b92974c8ce980 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 19 Sep 2023 11:14:49 +0800
Subject: [PATCH 304/397] type: fix React.Key type errors (#759)

---
 src/Tree.tsx          | 2 +-
 src/contextTypes.ts   | 2 +-
 src/interface.tsx     | 8 ++++----
 src/utils/treeUtil.ts | 2 +-
 tests/Tree.spec.tsx   | 2 +-
 5 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 0dee8cdf..fa4ed798 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -205,7 +205,7 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
   loadingKeys: Key[];
   expandedKeys: Key[];
 
-  draggingNodeKey: React.Key;
+  draggingNodeKey: Key;
   dragChildrenKeys: Key[];
 
   // for details see comment in Tree.state
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 1fff4f21..fbb21e4c 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -46,7 +46,7 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
   icon: IconType;
   switcherIcon: IconType;
   draggable?: DraggableConfig;
-  draggingNodeKey?: React.Key;
+  draggingNodeKey?: Key;
   checkable: boolean | React.ReactNode;
   checkStrictly: boolean;
   disabled: boolean;
diff --git a/src/interface.tsx b/src/interface.tsx
index 586cac3c..2a820610 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -59,13 +59,15 @@ export type FieldDataNode<T, ChildFieldName extends string = 'children'> = Basic
   T &
   Partial<Record<ChildFieldName, FieldDataNode<T, ChildFieldName>[]>>;
 
+export type Key = string | number;
+
 export type DataNode = FieldDataNode<{
-  key: string | number;
+  key: Key;
   title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
 }>;
 
 export type EventDataNode<TreeDataType> = {
-  key: React.Key;
+  key: Key;
   expanded: boolean;
   selected: boolean;
   checked: boolean;
@@ -82,8 +84,6 @@ export type EventDataNode<TreeDataType> = {
 
 export type IconType = React.ReactNode | ((props: TreeNodeProps) => React.ReactNode);
 
-export type Key = string | number;
-
 export type NodeElement = React.ReactElement<TreeNodeProps> & {
   selectHandle?: HTMLSpanElement;
   type: {
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 5ea0b6cf..3de006fb 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -89,7 +89,7 @@ export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
         const { children, ...rest } = treeNode.props;
 
         const dataNode: DataNode = {
-          key,
+          key: key as Key,
           ...rest,
         };
 
diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index a112914f..5d925616 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -432,7 +432,7 @@ describe('Tree Basic', () => {
     // https://github.com/ant-design/ant-design/issues/7353
     it('check children after changing from children[disableCheckbox] from true to false', () => {
       let checkedKeys = null;
-      const mockHandleCheck = (keys: React.Key[]) => (checkedKeys = keys);
+      const mockHandleCheck = (keys: string[]) => (checkedKeys = keys);
       function Test({ disableCheckbox }) {
         return (
           <Tree checkable onCheck={mockHandleCheck}>

From 08e217dee9a32beff8c017dab4887479334b0100 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 19 Sep 2023 11:17:06 +0800
Subject: [PATCH 305/397] 5.7.11

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 6e8914d1..abd77d88 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.10",
+  "version": "5.7.11",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 01bbc3178b4c9caec39ee2b48fb4e09ff1cc23c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Tue, 19 Sep 2023 14:21:54 +0800
Subject: [PATCH 306/397] chore: follow up types update (#760)

* chore: tmp of it

* chore: tmp of it

* chore: tmp of keys

* chore: use func

* chore: use func type

* chore: wrap inline
---
 src/NodeList.tsx         | 12 +++++-
 src/Tree.tsx             | 83 +++++++++++++++++++++-------------------
 src/TreeNode.tsx         |  5 ++-
 src/contextTypes.ts      | 12 +++---
 src/interface.tsx        | 12 +++++-
 src/util.tsx             | 37 ++++++++++--------
 src/utils/conductUtil.ts | 14 +++++--
 src/utils/keyUtil.ts     |  5 +++
 src/utils/treeUtil.ts    | 11 ++++--
 9 files changed, 117 insertions(+), 74 deletions(-)
 create mode 100644 src/utils/keyUtil.ts

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 6bf73eb0..65bb9391 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -5,7 +5,15 @@
 import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
 import VirtualList, { ListRef } from 'rc-virtual-list';
 import * as React from 'react';
-import { BasicDataNode, DataEntity, DataNode, FlattenNode, Key, ScrollTo } from './interface';
+import {
+  BasicDataNode,
+  DataEntity,
+  DataNode,
+  FlattenNode,
+  Key,
+  KeyEntities,
+  ScrollTo,
+} from './interface';
 import MotionTreeNode from './MotionTreeNode';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
 import { getKey, getTreeNodeProps } from './utils/treeUtil';
@@ -74,7 +82,7 @@ interface NodeListProps<TreeDataType extends BasicDataNode> {
   loadedKeys: Key[];
   loadingKeys: Key[];
   halfCheckedKeys: Key[];
-  keyEntities: Record<Key, DataEntity<any>>;
+  keyEntities: KeyEntities;
 
   dragging: boolean;
   dragOverNodeKey: Key;
diff --git a/src/Tree.tsx b/src/Tree.tsx
index fa4ed798..4a6e71c6 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -1,55 +1,57 @@
 // TODO: https://www.w3.org/TR/2017/NOTE-wai-aria-practices-1.1-20171214/examples/treeview/treeview-2/treeview-2a.html
 // Fully accessibility support
 
-import * as React from 'react';
+import classNames from 'classnames';
 import KeyCode from 'rc-util/lib/KeyCode';
-import warning from 'rc-util/lib/warning';
 import pickAttrs from 'rc-util/lib/pickAttrs';
-import classNames from 'classnames';
+import warning from 'rc-util/lib/warning';
+import * as React from 'react';
 
 import {
-  TreeContext,
-  NodeMouseEventHandler,
   NodeDragEventHandler,
   NodeDragEventParams,
+  NodeMouseEventHandler,
   NodeMouseEventParams,
+  TreeContext,
 } from './contextTypes';
+import DropIndicator from './DropIndicator';
 import {
-  getDragChildrenKeys,
-  parseCheckedKeys,
-  conductExpandParent,
-  calcSelectedKeys,
-  calcDropPosition,
-  arrAdd,
-  arrDel,
-  posToArr,
-} from './util';
-import {
+  BasicDataNode,
   DataNode,
+  Direction,
+  EventDataNode,
+  FieldNames,
+  FlattenNode,
   IconType,
   Key,
-  FlattenNode,
-  DataEntity,
-  EventDataNode,
+  KeyEntities,
   NodeInstance,
+  SafeKey,
   ScrollTo,
-  Direction,
-  FieldNames,
-  BasicDataNode,
 } from './interface';
+import NodeList, { MotionEntity, MOTION_KEY, NodeListRef } from './NodeList';
+import TreeNode from './TreeNode';
+import {
+  arrAdd,
+  arrDel,
+  calcDropPosition,
+  calcSelectedKeys,
+  conductExpandParent,
+  getDragChildrenKeys,
+  parseCheckedKeys,
+  posToArr,
+} from './util';
+import { conductCheck } from './utils/conductUtil';
+import getEntity from './utils/keyUtil';
 import {
-  flattenTreeData,
-  convertTreeToData,
   convertDataToEntities,
-  warningWithoutKey,
   convertNodePropsToEventData,
-  getTreeNodeProps,
+  convertTreeToData,
   fillFieldNames,
+  flattenTreeData,
+  getTreeNodeProps,
+  warningWithoutKey,
 } from './utils/treeUtil';
-import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
-import TreeNode from './TreeNode';
-import { conductCheck } from './utils/conductUtil';
-import DropIndicator from './DropIndicator';
 
 const MAX_RETRY_TIMES = 10;
 
@@ -194,7 +196,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
 }
 
 interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
-  keyEntities: Record<Key, DataEntity<TreeDataType>>;
+  keyEntities: KeyEntities<TreeDataType>;
 
   indent: number | null;
 
@@ -234,7 +236,7 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
 class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends React.Component<
   TreeProps<TreeDataType>,
   TreeState<TreeDataType>
-  > {
+> {
   static defaultProps = {
     prefixCls: 'rc-tree',
     showLine: false,
@@ -260,9 +262,9 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
   destroyed: boolean = false;
 
-  delayedDragEnterLogic: Record<Key, number>;
+  delayedDragEnterLogic: Record<SafeKey, number>;
 
-  loadingRetryTimes: Record<Key, number> = {};
+  loadingRetryTimes: Record<SafeKey, number> = {};
 
   state: TreeState<TreeDataType> = {
     keyEntities: {},
@@ -560,7 +562,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
         if (this.state.draggingNodeKey === null) return;
 
         let newExpandedKeys = [...expandedKeys];
-        const entity = keyEntities[node.props.eventKey];
+        const entity = getEntity(keyEntities, node.props.eventKey);
 
         if (entity && (entity.children || []).length) {
           newExpandedKeys = arrAdd(expandedKeys, node.props.eventKey);
@@ -736,7 +738,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     const abstractDropNodeProps = {
       ...getTreeNodeProps(dropTargetKey, this.getTreeNodeRequiredProps()),
       active: this.getActiveItem()?.key === dropTargetKey,
-      data: this.state.keyEntities[dropTargetKey].node,
+      data: getEntity(this.state.keyEntities, dropTargetKey).node,
     };
     const dropToChild = dragChildrenKeys.indexOf(dropTargetKey) !== -1;
 
@@ -850,7 +852,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     // [Legacy] Not found related usage in doc or upper libs
     const selectedNodes = selectedKeys
       .map(selectedKey => {
-        const entity = keyEntities[selectedKey];
+        const entity = getEntity(keyEntities, selectedKey);
         if (!entity) return null;
 
         return entity.node;
@@ -896,7 +898,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       checkedObj = { checked: checkedKeys, halfChecked: halfCheckedKeys };
 
       eventObj.checkedNodes = checkedKeys
-        .map(checkedKey => keyEntities[checkedKey])
+        .map(checkedKey => getEntity(keyEntities, checkedKey))
         .filter(entity => entity)
         .map(entity => entity.node);
 
@@ -928,7 +930,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       eventObj.halfCheckedKeys = halfCheckedKeys;
 
       checkedKeys.forEach(checkedKey => {
-        const entity = keyEntities[checkedKey];
+        const entity = getEntity(keyEntities, checkedKey);
         if (!entity) return;
 
         const { node, pos } = entity;
@@ -992,8 +994,9 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
             }));
 
             // If exceed max retry times, we give up retry
-            this.loadingRetryTimes[key] = (this.loadingRetryTimes[key] || 0) + 1;
-            if (this.loadingRetryTimes[key] >= MAX_RETRY_TIMES) {
+            this.loadingRetryTimes[key as SafeKey] =
+              (this.loadingRetryTimes[key as SafeKey] || 0) + 1;
+            if (this.loadingRetryTimes[key as SafeKey] >= MAX_RETRY_TIMES) {
               const { loadedKeys: currentLoadedKeys } = this.state;
 
               warning(false, 'Retry for `loadData` many times but still failed. No more retry.');
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 953f4ec0..20494bdf 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -5,6 +5,7 @@ import * as React from 'react';
 import { TreeContext, TreeContextProps } from './contextTypes';
 import Indent from './Indent';
 import { TreeNodeProps } from './interface';
+import getEntity from './utils/keyUtil';
 import { convertNodePropsToEventData } from './utils/treeUtil';
 
 const ICON_OPEN = 'open';
@@ -209,7 +210,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     const {
       context: { keyEntities },
     } = this.props;
-    const { children } = keyEntities[eventKey] || {};
+    const { children } = getEntity(keyEntities, eventKey) || {};
 
     return !!(children || []).length;
   };
@@ -534,7 +535,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     } = this.props;
     const disabled = this.isDisabled();
     const dataOrAriaAttributeProps = pickAttrs(otherProps, { aria: true, data: true });
-    const { level } = keyEntities[eventKey] || {};
+    const { level } = getEntity(keyEntities, eventKey) || {};
     const isEndNode = isEnd[isEnd.length - 1];
 
     const mergedDraggable = this.isDraggable();
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index fbb21e4c..2132ff26 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -4,14 +4,14 @@
  */
 import * as React from 'react';
 import {
+  BasicDataNode,
+  DataNode,
+  Direction,
+  EventDataNode,
   IconType,
   Key,
-  DataEntity,
-  EventDataNode,
+  KeyEntities,
   NodeInstance,
-  DataNode,
-  Direction,
-  BasicDataNode,
 } from './interface';
 import { DraggableConfig } from './Tree';
 
@@ -50,7 +50,7 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
   checkable: boolean | React.ReactNode;
   checkStrictly: boolean;
   disabled: boolean;
-  keyEntities: Record<Key, DataEntity<any>>;
+  keyEntities: KeyEntities;
   // for details see comment in Tree.state (Tree.tsx)
   dropLevelOffset?: number;
   dropContainerKey: Key | null;
diff --git a/src/interface.tsx b/src/interface.tsx
index 2a820610..6213e860 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -59,7 +59,17 @@ export type FieldDataNode<T, ChildFieldName extends string = 'children'> = Basic
   T &
   Partial<Record<ChildFieldName, FieldDataNode<T, ChildFieldName>[]>>;
 
-export type Key = string | number;
+export type Key = React.Key;
+
+/**
+ * Typescript not support `bigint` as index type yet.
+ * We use this to mark the `bigint` type is for `Key` usage.
+ * It's safe to remove this when typescript fix:
+ * https://github.com/microsoft/TypeScript/issues/50217
+ */
+export type SafeKey = Exclude<Key, bigint>;
+
+export type KeyEntities<DateType = any> = Record<SafeKey, DataEntity<DateType>>;
 
 export type DataNode = FieldDataNode<{
   key: Key;
diff --git a/src/util.tsx b/src/util.tsx
index 6b8b1ef2..4b50d93c 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -3,20 +3,22 @@
  * Legacy code. Should avoid to use if you are new to import these code.
  */
 
-import React from 'react';
 import warning from 'rc-util/lib/warning';
-import TreeNode from './TreeNode';
+import React from 'react';
 import {
-  NodeElement,
-  Key,
-  DataNode,
+  BasicDataNode,
   DataEntity,
-  NodeInstance,
-  FlattenNode,
+  DataNode,
   Direction,
-  BasicDataNode,
+  FlattenNode,
+  Key,
+  KeyEntities,
+  NodeElement,
+  NodeInstance,
 } from './interface';
-import { TreeProps, AllowDrop } from './Tree';
+import { AllowDrop, TreeProps } from './Tree';
+import TreeNode from './TreeNode';
+import getEntity from './utils/keyUtil';
 
 export { getPosition, isTreeNode } from './utils/treeUtil';
 
@@ -44,13 +46,13 @@ export function posToArr(pos: string) {
 
 export function getDragChildrenKeys<TreeDataType extends BasicDataNode = DataNode>(
   dragNodeKey: Key,
-  keyEntities: Record<Key, DataEntity<TreeDataType>>,
+  keyEntities: KeyEntities<TreeDataType>,
 ): Key[] {
   // not contains self
   // self for left or right drag
   const dragChildrenKeys = [];
 
-  const entity = keyEntities[dragNodeKey];
+  const entity = getEntity(keyEntities, dragNodeKey);
   function dig(list: DataEntity<TreeDataType>[] = []) {
     list.forEach(({ key, children }) => {
       dragChildrenKeys.push(key);
@@ -92,7 +94,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
   },
   allowDrop: AllowDrop<TreeDataType>,
   flattenedNodes: FlattenNode<TreeDataType>[],
-  keyEntities: Record<Key, DataEntity<TreeDataType>>,
+  keyEntities: KeyEntities<TreeDataType>,
   expandKeys: Key[],
   direction: Direction,
 ): {
@@ -112,7 +114,10 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
   const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent;
 
   // find abstract drop node by horizontal offset
-  let abstractDropNodeEntity: DataEntity<TreeDataType> = keyEntities[targetNode.props.eventKey];
+  let abstractDropNodeEntity: DataEntity<TreeDataType> = getEntity(
+    keyEntities,
+    targetNode.props.eventKey,
+  );
 
   if (clientY < top + height / 2) {
     // first half, set abstract drop node to previous node
@@ -121,7 +126,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
     );
     const prevNodeIndex = nodeIndex <= 0 ? 0 : nodeIndex - 1;
     const prevNodeKey = flattenedNodes[prevNodeIndex].key;
-    abstractDropNodeEntity = keyEntities[prevNodeKey];
+    abstractDropNodeEntity = getEntity(keyEntities, prevNodeKey);
   }
 
   const initialAbstractDropNodeKey = abstractDropNodeEntity.key;
@@ -326,13 +331,13 @@ export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Ke
  * @param keyList
  * @param keyEntities
  */
-export function conductExpandParent(keyList: Key[], keyEntities: Record<Key, DataEntity>): Key[] {
+export function conductExpandParent(keyList: Key[], keyEntities: KeyEntities): Key[] {
   const expandedKeys = new Set<Key>();
 
   function conductUp(key: Key) {
     if (expandedKeys.has(key)) return;
 
-    const entity = keyEntities[key];
+    const entity = getEntity(keyEntities, key);
     if (!entity) return;
 
     expandedKeys.add(key);
diff --git a/src/utils/conductUtil.ts b/src/utils/conductUtil.ts
index c5d8d3f3..9f1fda9c 100644
--- a/src/utils/conductUtil.ts
+++ b/src/utils/conductUtil.ts
@@ -1,5 +1,13 @@
 import warning from 'rc-util/lib/warning';
-import { Key, DataEntity, DataNode, GetCheckDisabled, BasicDataNode } from '../interface';
+import {
+  BasicDataNode,
+  DataEntity,
+  DataNode,
+  GetCheckDisabled,
+  Key,
+  KeyEntities,
+} from '../interface';
+import getEntity from './keyUtil';
 
 interface ConductReturnType {
   checkedKeys: Key[];
@@ -185,7 +193,7 @@ function cleanConductCheck<TreeDataType extends BasicDataNode = DataNode>(
 export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
   keyList: Key[],
   checked: true | { checked: false; halfCheckedKeys: Key[] },
-  keyEntities: Record<Key, DataEntity<TreeDataType>>,
+  keyEntities: KeyEntities<TreeDataType>,
   getCheckDisabled?: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
   const warningMissKeys: Key[] = [];
@@ -200,7 +208,7 @@ export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
   // We only handle exist keys
   const keys = new Set<Key>(
     keyList.filter(key => {
-      const hasEntity = !!keyEntities[key];
+      const hasEntity = !!getEntity(keyEntities, key);
       if (!hasEntity) {
         warningMissKeys.push(key);
       }
diff --git a/src/utils/keyUtil.ts b/src/utils/keyUtil.ts
new file mode 100644
index 00000000..ae464e16
--- /dev/null
+++ b/src/utils/keyUtil.ts
@@ -0,0 +1,5 @@
+import type { Key, KeyEntities, SafeKey } from '../interface';
+
+export default function getEntity<T = any>(keyEntities: KeyEntities<T>, key: Key) {
+  return keyEntities[key as SafeKey];
+}
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 3de006fb..ce42ea7e 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -11,9 +11,12 @@ import {
   FlattenNode,
   GetKey,
   Key,
+  KeyEntities,
   NodeElement,
+  SafeKey,
   TreeNodeProps,
 } from '../interface';
+import getEntity from './keyUtil';
 
 export function getPosition(level: string | number, index: number) {
   return `${level}-${index}`;
@@ -277,7 +280,7 @@ export function traverseDataNodes(
 
 interface Wrapper {
   posEntities: Record<string, DataEntity>;
-  keyEntities: Record<Key, DataEntity>;
+  keyEntities: KeyEntities;
 }
 
 /**
@@ -326,7 +329,7 @@ export function convertDataToEntities(
       const mergedKey = getKey(key, pos);
 
       posEntities[pos] = entity;
-      keyEntities[mergedKey] = entity;
+      keyEntities[mergedKey as SafeKey] = entity;
 
       // Fill children
       entity.parent = posEntities[parentPos];
@@ -358,7 +361,7 @@ export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = Data
   halfCheckedKeys: Key[];
   dragOverNodeKey: Key;
   dropPosition: number;
-  keyEntities: Record<Key, DataEntity<TreeDataType>>;
+  keyEntities: KeyEntities<TreeDataType>;
 }
 
 /**
@@ -378,7 +381,7 @@ export function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(
     keyEntities,
   }: TreeNodeRequiredProps<TreeDataType>,
 ) {
-  const entity = keyEntities[key];
+  const entity = getEntity(keyEntities, key);
 
   const treeNodeProps = {
     eventKey: key,

From 9269acc8a869e3905f339ddc955d6bd8c6966646 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 19 Sep 2023 14:24:13 +0800
Subject: [PATCH 307/397] 5.7.12

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index abd77d88..a24b89d7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.11",
+  "version": "5.7.12",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From a351ca37a9c5e0e9308a87b711bf64d1f71ffdad Mon Sep 17 00:00:00 2001
From: tubs-coder <119929626+tubs-coder@users.noreply.github.com>
Date: Mon, 9 Oct 2023 12:16:43 +1000
Subject: [PATCH 308/397] Fix async issue with scrollTo test (#764)

Co-authored-by: Mark Taylor <mark.taylor@deswik.com>
---
 tests/Tree.spec.tsx | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index 5d925616..a0d721ad 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -1,7 +1,7 @@
 /* eslint-disable no-undef, react/no-multi-comp, no-console,
 react/no-unused-state, react/prop-types, no-return-assign */
 import React from 'react';
-import { render, fireEvent } from '@testing-library/react';
+import {render, fireEvent, act} from '@testing-library/react';
 import { resetWarned } from 'rc-util/lib/warning';
 import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
 import Tree, { TreeNode } from '../src';
@@ -1054,7 +1054,10 @@ describe('Tree Basic', () => {
       const treeRef = React.createRef<any>();
       render(<Tree ref={treeRef} />);
 
-      treeRef.current.scrollTo({ key: 'light', align: 'top' });
+      act(() => {
+        treeRef.current.scrollTo({key: 'light', align: 'top'});
+      });
+      
       jest.runAllTimers();
 
       expect(called).toBeTruthy();

From f45c5576d46312dec4ed925fb95d22894495ac5b Mon Sep 17 00:00:00 2001
From: tubs-coder <119929626+tubs-coder@users.noreply.github.com>
Date: Mon, 9 Oct 2023 12:52:13 +1000
Subject: [PATCH 309/397] Webpack 4 and Node 17 incompatibility fix.  Can be
 removed once webpack is upgraded (#765)

Co-authored-by: Mark Taylor <mark.taylor@deswik.com>
---
 .npmrc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 .npmrc

diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000..c2e0187b
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+﻿node-options="--openssl-legacy-provider"
\ No newline at end of file

From 833cd4695ffcd72200b9e05c3b8a51f31dd6582a Mon Sep 17 00:00:00 2001
From: tubs-coder <119929626+tubs-coder@users.noreply.github.com>
Date: Mon, 9 Oct 2023 13:50:28 +1000
Subject: [PATCH 310/397] Improve type information for onClick and
 onDoubleClick handlers (#762)

* Improve type information for onClick and onDoubleClick handlers

Improve type information for onClick and onDoubleClick handlers.  Pass through generics.

* Ensure generics are passed to node handlers for correct typing

---------

Co-authored-by: Mark Taylor <mark.taylor@deswik.com>
---
 src/Tree.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 4a6e71c6..a6163431 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -124,8 +124,8 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
   onContextMenu?: React.MouseEventHandler<HTMLDivElement>;
-  onClick?: NodeMouseEventHandler;
-  onDoubleClick?: NodeMouseEventHandler;
+  onClick?: NodeMouseEventHandler<TreeDataType>;
+  onDoubleClick?: NodeMouseEventHandler<TreeDataType>;
   onScroll?: React.UIEventHandler<HTMLElement>;
   onExpand?: (
     expandedKeys: Key[],
@@ -812,7 +812,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     this.onNodeExpand(e as React.MouseEvent<HTMLDivElement>, eventNode);
   };
 
-  onNodeClick: NodeMouseEventHandler = (e, treeNode) => {
+  onNodeClick: NodeMouseEventHandler<TreeDataType> = (e, treeNode) => {
     const { onClick, expandAction } = this.props;
 
     if (expandAction === 'click') {
@@ -822,7 +822,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     onClick?.(e, treeNode);
   };
 
-  onNodeDoubleClick: NodeMouseEventHandler = (e, treeNode) => {
+  onNodeDoubleClick: NodeMouseEventHandler<TreeDataType> = (e, treeNode) => {
     const { onDoubleClick, expandAction } = this.props;
 
     if (expandAction === 'doubleClick') {

From 4890b5ddc2ad9b683de03619f00a641b3c290424 Mon Sep 17 00:00:00 2001
From: Jean-Louis Jouannic <862310+jljouannic@users.noreply.github.com>
Date: Tue, 10 Oct 2023 04:04:30 +0200
Subject: [PATCH 311/397] =?UTF-8?q?Fixes=20transpilation=20error=E2=80=A6?=
 =?UTF-8?q?=20(#763)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

… on project using rc-tree (directly or not) with option `skipLibCheck` set to `false`
---
 src/interface.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/interface.tsx b/src/interface.tsx
index 6213e860..ccfbab08 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -69,7 +69,7 @@ export type Key = React.Key;
  */
 export type SafeKey = Exclude<Key, bigint>;
 
-export type KeyEntities<DateType = any> = Record<SafeKey, DataEntity<DateType>>;
+export type KeyEntities<DateType extends BasicDataNode = any> = Record<SafeKey, DataEntity<DateType>>;
 
 export type DataNode = FieldDataNode<{
   key: Key;

From 0f7e8c00ab595ae8e7edbb094f04010fe0d7a5f6 Mon Sep 17 00:00:00 2001
From: JiaQi <112228030+Yuiai01@users.noreply.github.com>
Date: Mon, 16 Oct 2023 21:38:40 +0800
Subject: [PATCH 312/397] feat: add item scroll offset property (#766)

Co-authored-by: dujiaqi <dujiaqi@kezaihui.com>
---
 src/Tree.tsx | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index a6163431..0a0d9d6b 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -186,6 +186,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   // Virtual List
   height?: number;
   itemHeight?: number;
+  itemScrollOffset?: number;
   virtual?: boolean;
 
   // direction for drag logic
@@ -326,13 +327,13 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
   }
 
   onUpdated() {
-    const { activeKey } = this.props;
+    const { activeKey, itemScrollOffset = 0 } = this.props;
 
     if (activeKey !== undefined && activeKey !== this.state.activeKey) {
       this.setState({ activeKey });
 
       if (activeKey !== null) {
-        this.scrollTo({ key: activeKey });
+        this.scrollTo({ key: activeKey, offset: itemScrollOffset });
       }
     }
   }

From c8ebc3fe6b3eb4bca59cf43f83158cf1f00358d4 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Tue, 17 Oct 2023 09:51:50 +0800
Subject: [PATCH 313/397] v5.8.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a24b89d7..5e331ee5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.7.12",
+  "version": "5.8.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 69e377e09ad4242810515df5b7e65f21f7540e85 Mon Sep 17 00:00:00 2001
From: JiaQi <112228030+Yuiai01@users.noreply.github.com>
Date: Mon, 23 Oct 2023 10:02:02 +0800
Subject: [PATCH 314/397] fix: activeChange add offset (#767)

* fix: add offset

* chore: add test case

---------

Co-authored-by: dujiaqi <dujiaqi@kezaihui.com>
---
 src/Tree.tsx        |  4 ++--
 tests/Tree.spec.tsx | 43 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 0a0d9d6b..3c60b413 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -1177,7 +1177,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
   // =========================== Keyboard ===========================
   onActiveChange = (newActiveKey: Key | null) => {
     const { activeKey } = this.state;
-    const { onActiveChange } = this.props;
+    const { onActiveChange, itemScrollOffset = 0 } = this.props;
 
     if (activeKey === newActiveKey) {
       return;
@@ -1185,7 +1185,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     this.setState({ activeKey: newActiveKey });
     if (newActiveKey !== null) {
-      this.scrollTo({ key: newActiveKey });
+      this.scrollTo({ key: newActiveKey, offset: itemScrollOffset });
     }
 
     onActiveChange?.(newActiveKey);
diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index a0d721ad..f0b70078 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -1065,6 +1065,49 @@ describe('Tree Basic', () => {
     });
   });
 
+  describe('offset should work', () => {
+    let domSpy: ReturnType<typeof spyElementPrototypes>;
+
+    beforeAll(() => {
+      domSpy = spyElementPrototypes(HTMLDivElement, {
+        clientHeight: {
+          get: () => 100,
+        },
+      });
+    });
+
+    afterAll(() => {
+      domSpy.mockRestore();
+    });
+
+    it('work', () => {
+      jest.useFakeTimers();
+
+      const data = [];
+      for (let i = 0; i < 10; i++) {
+        data.push({
+          key: i,
+          title: i,
+        });
+      }
+
+      const treeRef = React.createRef<any>();
+      
+      const { container } = render(
+        <Tree ref={treeRef} treeData={data} virtual itemHeight={20} height={100} />,
+      );
+
+      act(() => {
+        treeRef.current.scrollTo({ key: 5, offset: 5 });
+        jest.runAllTimers();
+      });
+      
+      expect(container.querySelector('.rc-tree-list-holder').scrollTop).toEqual(25);
+
+      jest.useRealTimers();
+    });
+  });
+
   it('not crash if expandedKeys is null', () => {
     render(
       <Tree expandedKeys={null}>

From 7c766b793f0aba8b076d24b361107ba30d0f62cb Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Mon, 23 Oct 2023 10:03:05 +0800
Subject: [PATCH 315/397] v5.8.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 5e331ee5..989674c6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.8.0",
+  "version": "5.8.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 0a76a6f35dfa161c8227bcb12a18fdf5f5a8a130 Mon Sep 17 00:00:00 2001
From: Amumu <yoyo837@hotmail.com>
Date: Thu, 26 Oct 2023 15:11:43 +0800
Subject: [PATCH 316/397] fix: fix ssr warning (#769)

---
 src/useUnmount.ts | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/useUnmount.ts b/src/useUnmount.ts
index a9492190..2c1ddc4f 100644
--- a/src/useUnmount.ts
+++ b/src/useUnmount.ts
@@ -1,4 +1,5 @@
 import * as React from 'react';
+import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
 
 /**
  * Trigger only when component unmount
@@ -6,7 +7,7 @@ import * as React from 'react';
 export default function useUnmount(triggerStart: VoidFunction, triggerEnd: VoidFunction) {
   const [firstMount, setFirstMount] = React.useState(false);
 
-  React.useLayoutEffect(() => {
+  useLayoutEffect(() => {
     if (firstMount) {
       triggerStart();
 
@@ -16,7 +17,7 @@ export default function useUnmount(triggerStart: VoidFunction, triggerEnd: VoidF
     }
   }, [firstMount]);
 
-  React.useLayoutEffect(() => {
+  useLayoutEffect(() => {
     setFirstMount(true);
 
     return () => {

From aa6849c4134d7b27b1b5a18fcc45497f92a89186 Mon Sep 17 00:00:00 2001
From: MadCcc <1075746765@qq.com>
Date: Thu, 26 Oct 2023 15:19:48 +0800
Subject: [PATCH 317/397] v5.8.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 989674c6..68e626ff 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.8.1",
+  "version": "5.8.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 8bed674b167e0c3d8a0121ada9682dae346074a9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 26 Nov 2023 01:39:25 +0800
Subject: [PATCH 318/397] chore(deps-dev): bump less from 3.13.1 to 4.1.3
 (#596)

Bumps [less](https://github.com/less/less.js) from 3.13.1 to 4.1.3.
- [Release notes](https://github.com/less/less.js/releases)
- [Changelog](https://github.com/less/less.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/less/less.js/commits)

---
updated-dependencies:
- dependency-name: less
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 68e626ff..27f75adb 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
     "eslint": "^7.0.0",
     "father": "^2.29.8",
     "glob": "^7.1.6",
-    "less": "^3.11.1",
+    "less": "^4.1.3",
     "np": "^6.0.0",
     "rc-dialog": "^8.1.0",
     "rc-tooltip": "5.x",

From 884830e53b8f71d65aeb124f13f893549702f3cb Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Sun, 26 Nov 2023 01:47:35 +0800
Subject: [PATCH 319/397] Revert "chore(deps-dev): bump less from 3.13.1 to
 4.1.3 (#596)" (#771)

This reverts commit 8bed674b167e0c3d8a0121ada9682dae346074a9.
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 27f75adb..68e626ff 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
     "eslint": "^7.0.0",
     "father": "^2.29.8",
     "glob": "^7.1.6",
-    "less": "^4.1.3",
+    "less": "^3.11.1",
     "np": "^6.0.0",
     "rc-dialog": "^8.1.0",
     "rc-tooltip": "5.x",

From 8ebf805c00bded6ceaa22b69594976400eb9b8f7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 5 Dec 2023 08:36:05 +0800
Subject: [PATCH 320/397] chore(deps-dev): bump @testing-library/jest-dom from
 5.17.0 to 6.1.5 (#782)

Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.17.0 to 6.1.5.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.17.0...v6.1.5)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 68e626ff..efa97126 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,7 @@
     "react-dom": "*"
   },
   "devDependencies": {
-    "@testing-library/jest-dom": "^5.16.4",
+    "@testing-library/jest-dom": "^6.1.5",
     "@testing-library/react": "^13.0.0",
     "@types/enzyme": "^3.10.9",
     "@types/jest": "^26.0.4",

From 7027cae085634921f6cd632c558b4021e52100d2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 5 Dec 2023 08:36:17 +0800
Subject: [PATCH 321/397] chore(deps-dev): bump @types/jest from 26.0.24 to
 29.5.10 (#781)

Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 26.0.24 to 29.5.10.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index efa97126..5ecfa40e 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
     "@testing-library/jest-dom": "^6.1.5",
     "@testing-library/react": "^13.0.0",
     "@types/enzyme": "^3.10.9",
-    "@types/jest": "^26.0.4",
+    "@types/jest": "^29.5.10",
     "@types/react": "^18.0.0",
     "@types/react-dom": "^17.0.8",
     "@types/warning": "^3.0.0",

From aae48680fd22965f4374b5594fb173328ea22fef Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 5 Dec 2023 08:36:29 +0800
Subject: [PATCH 322/397] chore(deps-dev): bump @types/react-dom from 17.0.25
 to 18.2.17 (#780)

Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 17.0.25 to 18.2.17.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 5ecfa40e..eb2bb37e 100644
--- a/package.json
+++ b/package.json
@@ -55,7 +55,7 @@
     "@types/enzyme": "^3.10.9",
     "@types/jest": "^29.5.10",
     "@types/react": "^18.0.0",
-    "@types/react-dom": "^17.0.8",
+    "@types/react-dom": "^18.2.17",
     "@types/warning": "^3.0.0",
     "@umijs/fabric": "^3.0.0",
     "dumi": "^1.1.4",

From 50315ad2a3a117ee3a8c5ac44ef67daf4b2313e1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 5 Dec 2023 08:36:42 +0800
Subject: [PATCH 323/397] chore(deps-dev): bump np from 6.5.0 to 9.0.0 (#775)

Bumps [np](https://github.com/sindresorhus/np) from 6.5.0 to 9.0.0.
- [Release notes](https://github.com/sindresorhus/np/releases)
- [Commits](https://github.com/sindresorhus/np/compare/v6.5.0...v9.0.0)

---
updated-dependencies:
- dependency-name: np
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index eb2bb37e..59c371c5 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
     "father": "^2.29.8",
     "glob": "^7.1.6",
     "less": "^3.11.1",
-    "np": "^6.0.0",
+    "np": "^9.0.0",
     "rc-dialog": "^8.1.0",
     "rc-tooltip": "5.x",
     "rc-trigger": "^5.0.7",

From 54d98da2bf5446253bc4ac91750b3d1297525ff9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 5 Dec 2023 08:52:07 +0800
Subject: [PATCH 324/397] chore(deps-dev): bump eslint from 7.32.0 to 8.55.0
 (#783)

* chore(deps-dev): bump eslint from 7.32.0 to 8.55.0

Bumps [eslint](https://github.com/eslint/eslint) from 7.32.0 to 8.55.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.32.0...v8.55.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update package.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: lijianan <574980606@qq.com>
---
 package.json | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 59c371c5..09b75284 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,9 @@
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",
     "enzyme-to-json": "^3.0.0",
-    "eslint": "^7.0.0",
+    "eslint": "^8.55.0",
+    "eslint-plugin-jest": "^27.6.0",
+    "eslint-plugin-unicorn": "^49.0.0",
     "father": "^2.29.8",
     "glob": "^7.1.6",
     "less": "^3.11.1",

From e660af4ab1831e769f403811e2910519753bcc5a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 16 Dec 2023 16:59:45 +0800
Subject: [PATCH 325/397] chore(deps-dev): bump typescript from 4.9.5 to 5.3.3
 (#788)

Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.9.5 to 5.3.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.9.5...v5.3.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 09b75284..150025fa 100644
--- a/package.json
+++ b/package.json
@@ -74,7 +74,7 @@
     "rc-trigger": "^5.0.7",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
-    "typescript": "^4.0.2"
+    "typescript": "^5.3.3"
   },
   "dependencies": {
     "@babel/runtime": "^7.10.1",

From 62d8cdce6cb5d3bf02b75af76cac7b1201c2fe76 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 16 Dec 2023 16:59:59 +0800
Subject: [PATCH 326/397] chore(deps-dev): bump @testing-library/react from
 13.4.0 to 14.1.2 (#786)

Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 13.4.0 to 14.1.2.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v13.4.0...v14.1.2)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 150025fa..663f48f7 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
   },
   "devDependencies": {
     "@testing-library/jest-dom": "^6.1.5",
-    "@testing-library/react": "^13.0.0",
+    "@testing-library/react": "^14.1.2",
     "@types/enzyme": "^3.10.9",
     "@types/jest": "^29.5.10",
     "@types/react": "^18.0.0",

From ebe4c94bcafa29c31085517f21d315f1e55b5415 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 16 Dec 2023 17:00:17 +0800
Subject: [PATCH 327/397] chore(deps-dev): bump @umijs/fabric from 3.0.0 to
 4.0.1 (#785)

Bumps [@umijs/fabric](https://github.com/umijs/fabric) from 3.0.0 to 4.0.1.
- [Release notes](https://github.com/umijs/fabric/releases)
- [Commits](https://github.com/umijs/fabric/compare/v3.0.0...v4.0.1)

---
updated-dependencies:
- dependency-name: "@umijs/fabric"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 663f48f7..5eccfdc9 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
     "@types/react": "^18.0.0",
     "@types/react-dom": "^18.2.17",
     "@types/warning": "^3.0.0",
-    "@umijs/fabric": "^3.0.0",
+    "@umijs/fabric": "^4.0.1",
     "dumi": "^1.1.4",
     "enzyme": "^3.3.0",
     "enzyme-adapter-react-16": "^1.1.1",

From 38901c1eca051220136f931deccf0827a643edf5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Mon, 29 Jan 2024 14:45:05 +0800
Subject: [PATCH 328/397] fix: Drag over expanded empty node should work as
 normal node (#800)

* fix: filter logic

* fix: ignore empty children

* test: update test

* docs: back of demo

* test: fix test case
---
 docs/examples/draggable.jsx                 |    4 +-
 src/Tree.tsx                                |    2 +-
 src/util.tsx                                |    7 +-
 tests/TreeDraggable.spec.tsx                | 1639 ++++++++++---------
 tests/TreeProps.spec.tsx                    |   11 +-
 tests/__snapshots__/TreeProps.spec.tsx.snap |    8 +-
 6 files changed, 861 insertions(+), 810 deletions(-)

diff --git a/docs/examples/draggable.jsx b/docs/examples/draggable.jsx
index 5809e598..965421ea 100644
--- a/docs/examples/draggable.jsx
+++ b/docs/examples/draggable.jsx
@@ -1,9 +1,9 @@
 /* eslint-disable no-console, react/no-access-state-in-setstate */
 import React from 'react';
-import { generateData } from './utils/dataUtil';
-import './draggable.less';
 import '../../assets/index.less';
 import Tree from '../../src';
+import './draggable.less';
+import { generateData } from './utils/dataUtil';
 
 const gData = generateData(2, 2, 2);
 
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 3c60b413..6c0455e0 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -637,7 +637,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     if (dragChildrenKeys.indexOf(dropTargetKey) !== -1 || !dropAllowed) {
       // don't allow drop inside its children
-      // don't allow drop when drop is not allowed caculated by calcDropPosition
+      // don't allow drop when drop is not allowed calculated by calcDropPosition
       return;
     }
 
diff --git a/src/util.tsx b/src/util.tsx
index 4b50d93c..5600c3d1 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -113,6 +113,9 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
     (direction === 'rtl' ? -1 : 1) * ((startMousePosition?.x || 0) - clientX);
   const rawDropLevelOffset = (horizontalMouseOffset - 12) / indent;
 
+  // Filter the expanded keys to exclude the node that not has children currently (like async nodes).
+  const filteredExpandKeys = expandKeys.filter((key: string) => keyEntities[key]?.children?.length);
+
   // find abstract drop node by horizontal offset
   let abstractDropNodeEntity: DataEntity<TreeDataType> = getEntity(
     keyEntities,
@@ -138,7 +141,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
   let dropLevelOffset = 0;
 
   // Only allow cross level drop when dragging on a non-expanded node
-  if (!expandKeys.includes(initialAbstractDropNodeKey)) {
+  if (!filteredExpandKeys.includes(initialAbstractDropNodeKey)) {
     for (let i = 0; i < rawDropLevelOffset; i += 1) {
       if (isLastChild(abstractDropNodeEntity)) {
         abstractDropNodeEntity = abstractDropNodeEntity.parent;
@@ -167,7 +170,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
     dropPosition = -1;
   } else if (
     (abstractDragOverEntity.children || []).length &&
-    expandKeys.includes(dragOverNodeKey)
+    filteredExpandKeys.includes(dragOverNodeKey)
   ) {
     // drop on expanded node
     // only allow drop inside
diff --git a/tests/TreeDraggable.spec.tsx b/tests/TreeDraggable.spec.tsx
index 21267bd3..2d5737cf 100644
--- a/tests/TreeDraggable.spec.tsx
+++ b/tests/TreeDraggable.spec.tsx
@@ -1,9 +1,8 @@
 /* eslint-disable no-undef, react/no-multi-comp, no-console,
 react/no-unused-state, react/prop-types, no-return-assign */
-import React from 'react';
-import { render, fireEvent, act, createEvent } from '@testing-library/react';
+import { act, createEvent, fireEvent, render } from '@testing-library/react';
 import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
-import Tree, { TreeNode, FieldDataNode } from '../src';
+import Tree, { FieldDataNode, TreeNode, TreeProps } from '../src';
 import { spyConsole } from './util';
 
 const delay = timeout =>
@@ -14,7 +13,7 @@ const delay = timeout =>
 describe('Tree Draggable', () => {
   spyConsole();
 
-  function createTree(props?: any) {
+  function createTree(props?: Partial<TreeProps>) {
     return (
       <Tree draggable defaultExpandAll {...props}>
         <TreeNode title="parent 1" key="0-0">
@@ -237,815 +236,827 @@ describe('Tree Draggable', () => {
     (['ltr', 'rtl'] as const).forEach(dir => {
       const base = dir === 'ltr' ? 1 : -1;
 
-      it('not allow cross level dnd on expanded nodes', () => {
-        const onDrop = jest.fn();
-        const { container } = render(
-          <Tree draggable onDrop={onDrop} direction={dir} expandedKeys={['0-0', '0-1', '0-1-0']}>
-            <TreeNode key="0-0">
-              <TreeNode key="0-0-0" className="dropTarget1">
-                <TreeNode key="0-0-0-0" />
+      describe(dir, () => {
+        it('not allow cross level dnd on expanded nodes', () => {
+          const onDrop = jest.fn();
+          const { container } = render(
+            <Tree draggable onDrop={onDrop} direction={dir} expandedKeys={['0-0', '0-1', '0-1-0']}>
+              <TreeNode key="0-0">
+                <TreeNode key="0-0-0" className="dropTarget1">
+                  <TreeNode key="0-0-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0" className="dropTarget2">
-                <TreeNode key="0-1-0-0" />
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0" className="dropTarget2">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" className="dragTarget" />
-          </Tree>,
-        );
-
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-
-        fireDragEvent(
-          container.querySelector('.dropTarget1 > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-
-        fireDragEvent(
-          container.querySelector('.dropTarget1 > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-
-        fireEvent.drop(container.querySelector('.dropTarget1 > .rc-tree-node-content-wrapper'));
-
-        // insert after 0-0, since 0-0-0 is not expanded
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
-
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-
-        fireDragEvent(
-          container.querySelector('.dropTarget2 > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-
-        fireDragEvent(
-          container.querySelector('.dropTarget2 > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-
-        fireEvent.drop(container.querySelector('.dropTarget2 > .rc-tree-node-content-wrapper'));
-
-        // insert into 0-1-0, since it is expanded, do not allow cross level dnd
-        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0');
-        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(0);
-      });
-
-      it('allowDrop all nodes', () => {
-        const onDrop = jest.fn();
-        const { container } = render(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              <TreeNode key="0-2" className="dragTarget" />
+            </Tree>,
+          );
+
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+
+          fireDragEvent(
+            container.querySelector('.dropTarget1 > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+
+          fireDragEvent(
+            container.querySelector('.dropTarget1 > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+
+          fireEvent.drop(container.querySelector('.dropTarget1 > .rc-tree-node-content-wrapper'));
+
+          // insert after 0-0, since 0-0-0 is not expanded
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0');
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+
+          fireDragEvent(
+            container.querySelector('.dropTarget2 > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+
+          fireDragEvent(
+            container.querySelector('.dropTarget2 > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+
+          fireEvent.drop(container.querySelector('.dropTarget2 > .rc-tree-node-content-wrapper'));
+
+          // insert into 0-1-0, since it is expanded, do not allow cross level dnd
+          expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0');
+          expect(onDrop.mock.calls[1][0].dropPosition).toEqual(0);
+        });
+
+        it('allowDrop all nodes', () => {
+          const onDrop = jest.fn();
+          const { container } = render(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" className="dropTarget" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" />
-          </Tree>,
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
-        // (in ltr) drag from right to left, should be insert after, so drop position is 1
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
-
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 500,
-            clientY: 600,
-          },
-        );
-
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 500,
-            clientY: 600,
-          },
-        );
-
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-
-        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
-
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 550,
-            clientY: 600,
-          },
-        );
-
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 550,
-            clientY: 600,
-          },
-        );
-
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
-
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 600,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 600,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
-      });
-
-      it('allowDrop no node', () => {
-        const onDrop = jest.fn();
-        const { container } = render(
-          <Tree draggable defaultExpandAll onDrop={onDrop} allowDrop={() => false} direction={dir}>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              <TreeNode key="0-2" />
+            </Tree>,
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 400,
+              clientY: 500,
+            },
+          );
+
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
+          // (in ltr) drag from right to left, should be insert after, so drop position is 1
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 500,
+              clientY: 600,
+            },
+          );
+
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 500,
+              clientY: 600,
+            },
+          );
+
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+
+          expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 550,
+              clientY: 600,
+            },
+          );
+
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 550,
+              clientY: 600,
+            },
+          );
+
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
+
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 600,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 600,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
+        });
+
+        it('allowDrop no node', () => {
+          const onDrop = jest.fn();
+          const { container } = render(
+            <Tree
+              draggable
+              defaultExpandAll
+              onDrop={onDrop}
+              allowDrop={() => false}
+              direction={dir}
+            >
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0" className="dropTargetParent">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0" className="dropTargetParent">
+                  <TreeNode key="0-1-0-0" className="dropTarget" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" />
-          </Tree>,
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTargetParent > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTargetParent > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(
-          container.querySelector('.dropTargetParent > .rc-tree-node-content-wrapper'),
-        );
-        // not allow any dropPosition except 0 on expanded node
-        expect(onDrop).not.toHaveBeenCalled();
-
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop).not.toHaveBeenCalled();
-
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 500,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 500,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop).not.toHaveBeenCalled();
-
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 550,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 550,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop).not.toHaveBeenCalled();
-
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 600,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 600,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop).not.toHaveBeenCalled();
-      });
-
-      it('drop to top half of first node', () => {
-        const onDrop = jest.fn();
-        const { container } = render(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
+              <TreeNode key="0-2" />
+            </Tree>,
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTargetParent > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTargetParent > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(
+            container.querySelector('.dropTargetParent > .rc-tree-node-content-wrapper'),
+          );
+          // not allow any dropPosition except 0 on expanded node
+          expect(onDrop).not.toHaveBeenCalled();
+
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop).not.toHaveBeenCalled();
+
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 500,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 500,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop).not.toHaveBeenCalled();
+
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 550,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 550,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop).not.toHaveBeenCalled();
+
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 600,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 600,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop).not.toHaveBeenCalled();
+        });
+
+        it('drop to top half of first node', () => {
+          const onDrop = jest.fn();
+          const { container } = render(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-1" className="dropTarget">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 400,
-            clientY: 0,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 400,
-            clientY: -1000,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
-      });
-
-      it('can drop on its direct parent', () => {
-        const onDrop = jest.fn();
-        const { container } = render(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
+            </Tree>,
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 400,
+              clientY: 0,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 400,
+              clientY: -1000,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
+        });
+
+        it('can drop on its direct parent', () => {
+          const onDrop = jest.fn();
+          const { container } = render(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-1" className="dropTarget">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dragTargetParent > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dragTargetParent > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireEvent.drop(
-          container.querySelector('.dragTargetParent > .rc-tree-node-content-wrapper'),
-        );
-        expect(onDrop).toHaveBeenCalled();
-      });
-
-      it('cover window dragend & componentWillUnmount', () => {
-        const { container, unmount } = render(
-          <Tree draggable defaultExpandAll direction={dir}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
+            </Tree>,
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dragTargetParent > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dragTargetParent > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireEvent.drop(
+            container.querySelector('.dragTargetParent > .rc-tree-node-content-wrapper'),
+          );
+          expect(onDrop).toHaveBeenCalled();
+        });
+
+        it('cover window dragend & componentWillUnmount', () => {
+          const { container, unmount } = render(
+            <Tree draggable defaultExpandAll direction={dir}>
+              <TreeNode key="0-1" className="dropTarget">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        window.dispatchEvent(new Event('dragend'));
-        unmount();
-      });
-
-      it('dragover first half of non-first child', () => {
-        const onDrop = jest.fn();
-        const { container } = render(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+            </Tree>,
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          window.dispatchEvent(new Event('dragend'));
+          unmount();
+        });
+
+        it('dragover first half of non-first child', () => {
+          const onDrop = jest.fn();
+          const { container } = render(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
+                <TreeNode key="0-0-1" />
+                <TreeNode key="0-0-2" className="dropTarget" />
               </TreeNode>
-              <TreeNode key="0-0-1" />
-              <TreeNode key="0-0-2" className="dropTarget" />
-            </TreeNode>
-          </Tree>,
-        );
-        // wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
-        //   clientX: base * 500,
-        //   clientY: 500,
-        // });
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        // wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
-        //   clientX: base * 500,
-        //   clientY: 1,
-        // });
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 500,
-            clientY: 1,
-          },
-        );
-        // wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
-        //   clientX: base * 500,
-        //   clientY: 1,
-        // });
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 500,
-            clientY: 1,
-          },
-        );
-        // wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
-      });
-
-      it('dragover self', () => {
-        const onDrop = jest.fn();
-        const { container } = render(
-          <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-1" className="dropTarget">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" />
+            </Tree>,
+          );
+          // wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
+          //   clientX: base * 500,
+          //   clientY: 500,
+          // });
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          // wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
+          //   clientX: base * 500,
+          //   clientY: 1,
+          // });
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 500,
+              clientY: 1,
+            },
+          );
+          // wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
+          //   clientX: base * 500,
+          //   clientY: 1,
+          // });
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 500,
+              clientY: 1,
+            },
+          );
+          // wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
+        });
+
+        it('dragover self', () => {
+          const onDrop = jest.fn();
+          const { container } = render(
+            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-1" className="dropTarget">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-0" className="dragTargetParent">
-              <TreeNode key="0-0-0" className="dragTarget">
-                <TreeNode key="0-0-0-0" className="dragTargetChild" />
+              <TreeNode key="0-0" className="dragTargetParent">
+                <TreeNode key="0-0-0" className="dragTarget">
+                  <TreeNode key="0-0-0-0" className="dragTargetChild" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-          </Tree>,
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 400,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 600,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 600,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 600,
-            clientY: 500,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop).not.toHaveBeenCalled();
-      });
-
-      it('not allowDrop on node which has children', () => {
-        const onDrop = jest.fn();
-        const allowDrop = ({ dropNode, dropPosition }) => {
-          if (!dropNode.children) {
-            if (dropPosition === 0) return false;
-          }
-          return true;
-        };
-        const { container } = render(
-          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-0" className="dragTarget">
-              <TreeNode key="0-0-0" className="dragTargetChild" />
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
+            </Tree>,
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 400,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 600,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 600,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 600,
+              clientY: 500,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop).not.toHaveBeenCalled();
+        });
+
+        it('not allowDrop on node which has children', () => {
+          const onDrop = jest.fn();
+          const allowDrop = ({ dropNode, dropPosition }) => {
+            if (!dropNode.children) {
+              if (dropPosition === 0) return false;
+            }
+            return true;
+          };
+          const { container } = render(
+            <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0" className="dragTarget">
+                <TreeNode key="0-0-0" className="dragTargetChild" />
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" />
-          </Tree>,
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
-        // (in ltr) drag from right to left, should be insert after, so drop position is 1
-        expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 500,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 500,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 550,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 550,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 600,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 600,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
-        expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
-      });
-
-      it('allowDrop should pass dragNode and dropNode', () => {
-        const onDrop = jest.fn();
-        const allowDrop = jest.fn();
-        const { container } = render(
-          <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
-            <TreeNode key="0-0" className="dragTarget">
-              <TreeNode key="0-0-0" className="dragTargetChild" />
-            </TreeNode>
-            <TreeNode key="0-1">
-              <TreeNode key="0-1-0">
-                <TreeNode key="0-1-0-0" className="dropTarget" />
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" className="dropTarget" />
+                </TreeNode>
               </TreeNode>
-            </TreeNode>
-            <TreeNode key="0-2" />
-          </Tree>,
-        );
-        fireDragEvent(
-          container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
-          'dragStart',
-          {
-            clientX: base * 500,
-            clientY: 500,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragEnter',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-        fireDragEvent(
-          container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
-          'dragOver',
-          {
-            clientX: base * 400,
-            clientY: 600,
-          },
-        );
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(allowDrop.mock.calls[0][0].dragNode.key).toEqual('0-0');
-        expect(allowDrop.mock.calls[0][0].dropNode.key).toEqual('0-1-0-0');
-      });
-
-      it('not allow dragging elements outside into tree', () => {
-        const onDrop = jest.fn();
-        const { container } = render(
-          <div>
-            <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
-              <TreeNode key="0-0">
-                <TreeNode key="0-0-0" className="dropTarget" />
+              <TreeNode key="0-2" />
+            </Tree>,
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 400,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
+          // (in ltr) drag from right to left, should be insert after, so drop position is 1
+          expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 500,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 500,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 550,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 550,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 500,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 600,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 600,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
+          expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
+        });
+
+        it('allowDrop should pass dragNode and dropNode', () => {
+          const onDrop = jest.fn();
+          const allowDrop = jest.fn();
+          const { container } = render(
+            <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
+              <TreeNode key="0-0" className="dragTarget">
+                <TreeNode key="0-0-0" className="dragTargetChild" />
               </TreeNode>
-            </Tree>
-            <div className="dragTarget">Element outside</div>
-          </div>,
-        );
-        fireEvent.dragStart(container.querySelector('.dragTarget'));
-        fireEvent.dragEnter(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        fireEvent.dragOver(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
-        expect(onDrop).not.toHaveBeenCalled();
+              <TreeNode key="0-1">
+                <TreeNode key="0-1-0">
+                  <TreeNode key="0-1-0-0" className="dropTarget" />
+                </TreeNode>
+              </TreeNode>
+              <TreeNode key="0-2" />
+            </Tree>,
+          );
+          fireDragEvent(
+            container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'),
+            'dragStart',
+            {
+              clientX: base * 400,
+              clientY: 500,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragEnter',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+          fireDragEvent(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+            'dragOver',
+            {
+              clientX: base * 400,
+              clientY: 600,
+            },
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(allowDrop.mock.calls[0][0].dragNode.key).toEqual('0-0');
+          expect(allowDrop.mock.calls[0][0].dropNode.key).toEqual('0-1-0-0');
+        });
+
+        it('not allow dragging elements outside into tree', () => {
+          const onDrop = jest.fn();
+          const { container } = render(
+            <div>
+              <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
+                <TreeNode key="0-0">
+                  <TreeNode key="0-0-0" className="dropTarget" />
+                </TreeNode>
+              </Tree>
+              <div className="dragTarget">Element outside</div>
+            </div>,
+          );
+          fireEvent.dragStart(container.querySelector('.dragTarget'));
+          fireEvent.dragEnter(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          );
+          fireEvent.dragOver(
+            container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'),
+          );
+          fireEvent.drop(container.querySelector('.dropTarget > .rc-tree-node-content-wrapper'));
+          expect(onDrop).not.toHaveBeenCalled();
+        });
       });
     });
   });
@@ -1147,4 +1158,42 @@ describe('Tree Draggable', () => {
     );
     expect(container.querySelectorAll('.rc-tree-treenode-draggable').length).toBe(3);
   });
+
+  it('not conflict with defaultExpandAll', () => {
+    const treeData = [
+      {
+        key: 1,
+        title: 'p1',
+        children: [
+          {
+            key: 2,
+            title: 'p2',
+            children: [],
+            className: 'dropTarget',
+          },
+        ],
+      },
+      {
+        key: 3,
+        title: 'p3',
+        children: [],
+      },
+      {
+        key: 4,
+        title: 'p4',
+        children: [],
+        className: 'dragTarget',
+      },
+    ];
+
+    const { container } = render(createTree({ defaultExpandAll: true, treeData }));
+    fireEvent.dragStart(container.querySelector('.dragTarget > .rc-tree-node-content-wrapper'));
+
+    // Drag over
+    fireDragEvent(container.querySelector('.dropTarget'), 'dragOver', {
+      clientX: -9999999,
+    });
+
+    expect(container.querySelector('.drop-target').textContent).toEqual('p1');
+  });
 });
diff --git a/tests/TreeProps.spec.tsx b/tests/TreeProps.spec.tsx
index 7b1fedfd..ef38052e 100644
--- a/tests/TreeProps.spec.tsx
+++ b/tests/TreeProps.spec.tsx
@@ -1,8 +1,8 @@
 /* eslint-disable no-undef, react/no-multi-comp */
-import React from 'react';
-import { render, fireEvent, act } from '@testing-library/react';
+import { act, fireEvent, render } from '@testing-library/react';
 import { resetWarned } from 'rc-util/lib/warning';
-import Tree, { TreeNode, FieldDataNode } from '../src';
+import React from 'react';
+import Tree, { FieldDataNode, TreeNode } from '../src';
 import { objectMatcher, spyConsole, spyError } from './util';
 
 /**
@@ -418,12 +418,11 @@ describe('Tree Props', () => {
   // draggable - is already full test in Tree.spec.js
   // autoExpandParent - is already full test in Tree.spec.js
 
-  // defaultExpandAll
   it('defaultExpandAll', () => {
     const { container } = render(
       <Tree defaultExpandAll>
-        <TreeNode key="0-0">
-          <TreeNode key="0-0-0" />
+        <TreeNode key="0-0" title="parent">
+          <TreeNode key="0-0-0" title="child" />
         </TreeNode>
       </Tree>,
     );
diff --git a/tests/__snapshots__/TreeProps.spec.tsx.snap b/tests/__snapshots__/TreeProps.spec.tsx.snap
index 9ba7de48..8a493b6e 100644
--- a/tests/__snapshots__/TreeProps.spec.tsx.snap
+++ b/tests/__snapshots__/TreeProps.spec.tsx.snap
@@ -853,7 +853,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
-              title="---"
+              title="parent"
             >
               <span
                 class="rc-tree-iconEle rc-tree-icon__open"
@@ -861,7 +861,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
               <span
                 class="rc-tree-title"
               >
-                ---
+                parent
               </span>
             </span>
           </div>
@@ -883,7 +883,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
-              title="---"
+              title="child"
             >
               <span
                 class="rc-tree-iconEle rc-tree-icon__docu"
@@ -891,7 +891,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
               <span
                 class="rc-tree-title"
               >
-                ---
+                child
               </span>
             </span>
           </div>

From 8f3efaa53e8ad082750b0bbbb6f3d4c74ebc399f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Mon, 29 Jan 2024 15:29:50 +0800
Subject: [PATCH 329/397] docs: use dumi2 & father4 (#801)

* test: rm enzyme

* docs: update dumi config

* chore: update ci

* chore: now config

* chore: fix ts
---
 .fatherrc.js                      | 14 +++++---------
 .github/workflows/main.yml        |  2 +-
 .gitignore                        |  2 ++
 .umirc.ts                         | 17 +++--------------
 docs/demo/animation-draggable.md  |  9 +++++++--
 docs/demo/animation.md            |  9 +++++++--
 docs/demo/basic-controlled.md     |  9 +++++++--
 docs/demo/basic.md                |  9 +++++++--
 docs/demo/big-data.md             |  9 +++++++--
 docs/demo/contextmenu.md          |  9 +++++++--
 docs/demo/custom-switch-icon.md   |  9 +++++++--
 docs/demo/draggable-allow-drop.md |  9 +++++++--
 docs/demo/draggable.md            |  9 +++++++--
 docs/demo/dropdown.md             |  9 +++++++--
 docs/demo/dynamic.md              |  9 +++++++--
 docs/demo/expandAction.md         |  9 +++++++--
 docs/demo/fieldNames.md           |  9 +++++++--
 docs/demo/funtionTitle.md         |  9 +++++++--
 docs/demo/icon.md                 |  9 +++++++--
 docs/demo/selectable.md           |  9 +++++++--
 jest.config.js                    |  1 -
 now.json                          |  2 +-
 package.json                      | 14 ++++++--------
 src/interface.tsx                 |  2 +-
 tests/setup.js                    | 17 -----------------
 tests/util.ts                     |  8 +-------
 tsconfig.json                     | 22 +++++++++++++++++-----
 27 files changed, 149 insertions(+), 96 deletions(-)
 delete mode 100644 tests/setup.js

diff --git a/.fatherrc.js b/.fatherrc.js
index f6810f73..96268ae1 100644
--- a/.fatherrc.js
+++ b/.fatherrc.js
@@ -1,9 +1,5 @@
-export default {
-  cjs: 'babel',
-  esm: { type: 'babel', importLibToEs: true },
-  preCommit: {
-    eslint: true,
-    prettier: false,
-  },
-  runtimeHelpers: true,
-};
+import { defineConfig } from 'father';
+
+export default defineConfig({
+  plugins: ['@rc-component/father-plugin'],
+});
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index e4edf95f..7d336a09 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -15,7 +15,7 @@ jobs:
 
       - uses: actions/setup-node@v1
         with:
-          node-version: '12'
+          node-version: '18'
 
       - name: cache package-lock.json
         uses: actions/cache@v2
diff --git a/.gitignore b/.gitignore
index 99bee95b..4126ce56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,5 @@ es/
 .umi-production
 .umi-test
 .env.local
+
+.dumi/
\ No newline at end of file
diff --git a/.umirc.ts b/.umirc.ts
index 2ec33e43..f734dd5c 100644
--- a/.umirc.ts
+++ b/.umirc.ts
@@ -2,18 +2,7 @@
 import { defineConfig } from 'dumi';
 
 export default defineConfig({
-  title: 'rc-tree',
-  favicon:
-    'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
-  logo:
-    'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
-  outputPath: '.doc',
-  exportStatic: {},
-  styles: [
-    `
-      .markdown table {
-        width: auto !important;
-      }
-    `,
-  ]
+  themeConfig: {
+    name: 'Tree',
+  },
 });
diff --git a/docs/demo/animation-draggable.md b/docs/demo/animation-draggable.md
index 4f7bf4f3..cd51870e 100644
--- a/docs/demo/animation-draggable.md
+++ b/docs/demo/animation-draggable.md
@@ -1,3 +1,8 @@
-## animation-draggable
+---
+title: Animation Draggable
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/animation-draggable.jsx">
+<code src="../examples/animation-draggable.jsx"></code>
diff --git a/docs/demo/animation.md b/docs/demo/animation.md
index ff20eb94..431fe072 100644
--- a/docs/demo/animation.md
+++ b/docs/demo/animation.md
@@ -1,3 +1,8 @@
-## animation
+---
+title: Animation
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/animation.jsx">
+<code src="../examples/animation.jsx"></code>
diff --git a/docs/demo/basic-controlled.md b/docs/demo/basic-controlled.md
index 17cf6c15..9ef9a3a0 100644
--- a/docs/demo/basic-controlled.md
+++ b/docs/demo/basic-controlled.md
@@ -1,3 +1,8 @@
-## basic-controlled
+---
+title: Basic Controlled
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/basic-controlled.jsx">
+<code src="../examples/basic-controlled.jsx"></code>
diff --git a/docs/demo/basic.md b/docs/demo/basic.md
index 80be196e..2bfed0cc 100644
--- a/docs/demo/basic.md
+++ b/docs/demo/basic.md
@@ -1,3 +1,8 @@
-## basic
+---
+title: Basic
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/basic.jsx">
+<code src="../examples/basic.jsx"></code>
diff --git a/docs/demo/big-data.md b/docs/demo/big-data.md
index ac3490a6..695474e6 100644
--- a/docs/demo/big-data.md
+++ b/docs/demo/big-data.md
@@ -1,3 +1,8 @@
-## big-data
+---
+title: Big Data
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/big-data.jsx">
+<code src="../examples/big-data.jsx"></code>
diff --git a/docs/demo/contextmenu.md b/docs/demo/contextmenu.md
index 551601d8..b088fceb 100644
--- a/docs/demo/contextmenu.md
+++ b/docs/demo/contextmenu.md
@@ -1,3 +1,8 @@
-## contextmenu
+---
+title: Context Menu
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/contextmenu.jsx">
+<code src="../examples/contextmenu.jsx"></code>
diff --git a/docs/demo/custom-switch-icon.md b/docs/demo/custom-switch-icon.md
index b0e63a13..81644e7e 100644
--- a/docs/demo/custom-switch-icon.md
+++ b/docs/demo/custom-switch-icon.md
@@ -1,3 +1,8 @@
-## custom-switch-icon
+---
+title: Custom Switch Icon
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/custom-switch-icon.jsx">
+<code src="../examples/custom-switch-icon.jsx"></code>
diff --git a/docs/demo/draggable-allow-drop.md b/docs/demo/draggable-allow-drop.md
index 29611ce6..2cbe21e4 100644
--- a/docs/demo/draggable-allow-drop.md
+++ b/docs/demo/draggable-allow-drop.md
@@ -1,3 +1,8 @@
-## draggable-allow-drop
+---
+title: Draggable Allow Drop
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/draggable-allow-drop.jsx">
+<code src="../examples/draggable-allow-drop.jsx"></code>
diff --git a/docs/demo/draggable.md b/docs/demo/draggable.md
index ab708b6a..5f167c75 100644
--- a/docs/demo/draggable.md
+++ b/docs/demo/draggable.md
@@ -1,3 +1,8 @@
-## draggable
+---
+title: Draggable
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/draggable.jsx">
+<code src="../examples/draggable.jsx"></code>
diff --git a/docs/demo/dropdown.md b/docs/demo/dropdown.md
index 8339a336..1ee32c19 100644
--- a/docs/demo/dropdown.md
+++ b/docs/demo/dropdown.md
@@ -1,3 +1,8 @@
-## dropdown
+---
+title: Dropdown
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/dropdown.jsx">
+<code src="../examples/dropdown.jsx"></code>
diff --git a/docs/demo/dynamic.md b/docs/demo/dynamic.md
index 5014c2a5..8470bbfe 100644
--- a/docs/demo/dynamic.md
+++ b/docs/demo/dynamic.md
@@ -1,3 +1,8 @@
-## dynamic
+---
+title: Dynamic
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/dynamic.jsx">
+<code src="../examples/dynamic.jsx"></code>
diff --git a/docs/demo/expandAction.md b/docs/demo/expandAction.md
index 8be97a03..902c8714 100644
--- a/docs/demo/expandAction.md
+++ b/docs/demo/expandAction.md
@@ -1,3 +1,8 @@
-## expandAction
+---
+title: Expand Action
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/expandAction.jsx">
+<code src="../examples/expandAction.jsx"></code>
diff --git a/docs/demo/fieldNames.md b/docs/demo/fieldNames.md
index 2d34a1c1..05f5f59c 100644
--- a/docs/demo/fieldNames.md
+++ b/docs/demo/fieldNames.md
@@ -1,3 +1,8 @@
-## Field Names
+---
+title: Field Names
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/fieldNames.tsx">
+<code src="../examples/fieldNames.tsx"></code>
diff --git a/docs/demo/funtionTitle.md b/docs/demo/funtionTitle.md
index 70f6f93b..385d6b7c 100644
--- a/docs/demo/funtionTitle.md
+++ b/docs/demo/funtionTitle.md
@@ -1,3 +1,8 @@
-## funtionTitle
+---
+title: Function Title
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/funtionTitle.jsx">
+<code src="../examples/funtionTitle.jsx"></code>
diff --git a/docs/demo/icon.md b/docs/demo/icon.md
index aeef622b..176aad6a 100644
--- a/docs/demo/icon.md
+++ b/docs/demo/icon.md
@@ -1,3 +1,8 @@
-## icon
+---
+title: Icon
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/icon.jsx">
+<code src="../examples/icon.jsx"></code>
diff --git a/docs/demo/selectable.md b/docs/demo/selectable.md
index 387ee44b..ad30c00e 100644
--- a/docs/demo/selectable.md
+++ b/docs/demo/selectable.md
@@ -1,3 +1,8 @@
-## selectable
+---
+title: Selectable
+nav:
+  title: Demo
+  path: /demo
+---
 
-<code src="../examples/selectable.jsx">
+<code src="../examples/selectable.jsx"></code>
diff --git a/jest.config.js b/jest.config.js
index b806b586..a4a97572 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,4 +1,3 @@
 module.exports = {
   setupFilesAfterEnv: ['<rootDir>/tests/setupFilesAfterEnv.js'],
-  snapshotSerializers: [require.resolve('enzyme-to-json/serializer')],
 };
diff --git a/now.json b/now.json
index 40647a1f..43ed1e5f 100644
--- a/now.json
+++ b/now.json
@@ -5,7 +5,7 @@
     {
       "src": "package.json",
       "use": "@now/static-build",
-      "config": { "distDir": ".doc" }
+      "config": { "distDir": "dist" }
     }
   ],
   "routes": [
diff --git a/package.json b/package.json
index 5eccfdc9..0dde6e78 100644
--- a/package.json
+++ b/package.json
@@ -40,8 +40,8 @@
     "prepublishOnly": "npm run compile && np --yolo --no-publish",
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
-    "test": "father test",
-    "coverage": "father test --coverage",
+    "test": "rc-test",
+    "coverage": "rc-test --coverage",
     "gh-pages": "npm run docs:build && father doc deploy",
     "now-build": "npm run docs:build"
   },
@@ -50,26 +50,24 @@
     "react-dom": "*"
   },
   "devDependencies": {
+    "@rc-component/father-plugin": "^1.0.2",
     "@testing-library/jest-dom": "^6.1.5",
     "@testing-library/react": "^14.1.2",
-    "@types/enzyme": "^3.10.9",
     "@types/jest": "^29.5.10",
     "@types/react": "^18.0.0",
     "@types/react-dom": "^18.2.17",
     "@types/warning": "^3.0.0",
     "@umijs/fabric": "^4.0.1",
-    "dumi": "^1.1.4",
-    "enzyme": "^3.3.0",
-    "enzyme-adapter-react-16": "^1.1.1",
-    "enzyme-to-json": "^3.0.0",
+    "dumi": "^2.1.0",
     "eslint": "^8.55.0",
     "eslint-plugin-jest": "^27.6.0",
     "eslint-plugin-unicorn": "^49.0.0",
-    "father": "^2.29.8",
+    "father": "^4.4.0",
     "glob": "^7.1.6",
     "less": "^3.11.1",
     "np": "^9.0.0",
     "rc-dialog": "^8.1.0",
+    "rc-test": "^7.0.15",
     "rc-tooltip": "5.x",
     "rc-trigger": "^5.0.7",
     "react": "^18.2.0",
diff --git a/src/interface.tsx b/src/interface.tsx
index ccfbab08..6c5e5ef4 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -1,6 +1,6 @@
 import * as React from 'react';
 
-export { ScrollTo } from 'rc-virtual-list/lib/List';
+export type { ScrollTo } from 'rc-virtual-list/lib/List';
 export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
   eventKey?: Key; // Pass by parent `cloneElement`
   prefixCls?: string;
diff --git a/tests/setup.js b/tests/setup.js
deleted file mode 100644
index b2e8b4df..00000000
--- a/tests/setup.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/* eslint-disable global-require */
-
-global.requestAnimationFrame = global.requestAnimationFrame || function requestAnimationFrame(cb) {
-  return setTimeout(cb, 0);
-};
-
-const Enzyme = require('enzyme');
-
-let Adapter;
-
-if (process.env.REACT === '15') {
-  Adapter = require('enzyme-adapter-react-15');
-} else {
-  Adapter = require('enzyme-adapter-react-16');
-}
-
-Enzyme.configure({ adapter: new Adapter() });
diff --git a/tests/util.ts b/tests/util.ts
index 65ef12e1..32782c3c 100644
--- a/tests/util.ts
+++ b/tests/util.ts
@@ -1,4 +1,3 @@
-import { ReactWrapper } from 'enzyme';
 import { Component } from 'react';
 
 export function objectMatcher(item: object) {
@@ -6,12 +5,7 @@ export function objectMatcher(item: object) {
 
   Object.keys(item).forEach(key => {
     const value = item[key];
-    if (
-      value &&
-      typeof value === 'object' &&
-      !(value instanceof ReactWrapper) &&
-      !(value instanceof Component)
-    ) {
+    if (value && typeof value === 'object' && !(value instanceof Component)) {
       result[key] = objectMatcher(value);
     } else {
       result[key] = value;
diff --git a/tsconfig.json b/tsconfig.json
index 592bf940..085f4904 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -8,9 +8,21 @@
     "skipLibCheck": true,
     "esModuleInterop": true,
     "paths": {
-      "@/*": ["src/*"],
-      "@@/*": ["src/.umi/*"],
-      "rc-tree": ["src/index.ts"]
+      "@/*": [
+        "src/*"
+      ],
+      "@@/*": [
+        "src/.umi/*"
+      ],
+      "rc-tree": [
+        "src/index.ts"
+      ]
     }
-  }
-}
+  },
+  "include": [
+    ".dumirc.ts",
+    "./src/**/*.ts",
+    "./src/**/*.tsx",
+    "./docs/**/*.tsx"
+  ]
+}
\ No newline at end of file

From 1a62cb2b01a4fd360402b259892656bb90a2f7a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 29 Jan 2024 15:38:30 +0800
Subject: [PATCH 330/397] 5.8.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0dde6e78..b6500eef 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.8.2",
+  "version": "5.8.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 86a1d1c734504540eb6ccb8f35ba5c4c221d09e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 29 Jan 2024 15:47:05 +0800
Subject: [PATCH 331/397] chore: update gh-pages config

---
 package.json | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index b6500eef..66793347 100644
--- a/package.json
+++ b/package.json
@@ -35,14 +35,14 @@
   "scripts": {
     "start": "dumi dev",
     "docs:build": "dumi build",
-    "docs:deploy": "gh-pages -d docs-dist",
+    "docs:deploy": "gh-pages -d dist",
     "compile": "father build && lessc assets/index.less assets/index.css",
     "prepublishOnly": "npm run compile && np --yolo --no-publish",
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
     "test": "rc-test",
     "coverage": "rc-test --coverage",
-    "gh-pages": "npm run docs:build && father doc deploy",
+    "gh-pages": "npm run docs:build && npm run docs:deploy",
     "now-build": "npm run docs:build"
   },
   "peerDependencies": {
@@ -63,6 +63,7 @@
     "eslint-plugin-jest": "^27.6.0",
     "eslint-plugin-unicorn": "^49.0.0",
     "father": "^4.4.0",
+    "gh-pages": "^6.1.1",
     "glob": "^7.1.6",
     "less": "^3.11.1",
     "np": "^9.0.0",

From 249abc426c852663e13524b3007a2b53edb9502e Mon Sep 17 00:00:00 2001
From: Amumu <yoyo837@hotmail.com>
Date: Tue, 30 Jan 2024 10:00:39 +0800
Subject: [PATCH 332/397] chore: fix npm package (#803)

---
 package.json | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/package.json b/package.json
index 66793347..d729750c 100644
--- a/package.json
+++ b/package.json
@@ -12,11 +12,7 @@
     "tree"
   ],
   "files": [
-    "assets/*.css",
-    "assets/*.png",
-    "assets/*.gif",
-    "assets/*.less",
-    "dist",
+    "assets",
     "es",
     "lib"
   ],

From d4781427da04a3ef0af97bebe4668b126f421ad2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 30 Jan 2024 10:01:28 +0800
Subject: [PATCH 333/397] 5.8.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d729750c..0d32a53a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.8.3",
+  "version": "5.8.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 762304e16a331148d1ef1dd3224369bb98d248f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 30 Jan 2024 10:06:44 +0800
Subject: [PATCH 334/397] 5.8.5

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0d32a53a..db7ff8ec 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.8.4",
+  "version": "5.8.5",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From f367b8024d82da6b8d20d075f34cfc1a2e1a94f1 Mon Sep 17 00:00:00 2001
From: mr-chenguang <37072324+lcgash@users.noreply.github.com>
Date: Mon, 19 Feb 2024 14:39:26 +0800
Subject: [PATCH 335/397] feat: support set tree indent width (#804)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat: support set tree indent width

* fix: remove console log

* fix: update snapshots and add demo

* fix: 补充 indentwidth 测试用例

* fix: 补充 indent 测试用例

* fix: 更新 snapshot

* fix: indent 没有自定义宽度的时候,回滚默认样式

* fix: change width style

Co-authored-by: Amumu <yoyo837@hotmail.com>

* fix: 调整 indent width 的测试用例

* fix: 去掉默认测试用例,保留自定义,调整部分代码

---------

Co-authored-by: Amumu <yoyo837@hotmail.com>
---
 docs/demo/custom-indent-width.md      |  8 +++
 docs/examples/custom-indent-width.jsx | 70 +++++++++++++++++++++++++++
 src/Indent.tsx                        |  8 ++-
 src/NodeList.tsx                      |  3 ++
 src/Tree.tsx                          |  4 ++
 src/TreeNode.tsx                      |  3 +-
 src/contextTypes.ts                   |  1 +
 src/interface.tsx                     |  1 +
 tests/TreeProps.spec.tsx              | 14 +++++-
 9 files changed, 109 insertions(+), 3 deletions(-)
 create mode 100644 docs/demo/custom-indent-width.md
 create mode 100644 docs/examples/custom-indent-width.jsx

diff --git a/docs/demo/custom-indent-width.md b/docs/demo/custom-indent-width.md
new file mode 100644
index 00000000..6f47a8e4
--- /dev/null
+++ b/docs/demo/custom-indent-width.md
@@ -0,0 +1,8 @@
+---
+title: Custom Indent Width
+nav:
+  title: Demo
+  path: /demo
+---
+
+<code src="../examples/custom-indent-width.jsx"></code>
diff --git a/docs/examples/custom-indent-width.jsx b/docs/examples/custom-indent-width.jsx
new file mode 100644
index 00000000..dd31a5cd
--- /dev/null
+++ b/docs/examples/custom-indent-width.jsx
@@ -0,0 +1,70 @@
+/* eslint-disable no-alert, no-console, react/no-find-dom-node */
+import React from 'react';
+import '../../assets/index.less';
+import './basic.less';
+import Tree from 'rc-tree';
+
+const treeData = [
+  {
+    key: '0-0',
+    title: 'parent 1',
+    children: [
+      { key: '0-0-0', title: 'parent 1-1', children: [{ key: '0-0-0-0', title: 'parent 1-1-0' }] },
+      {
+        key: '0-0-1',
+        title: 'parent 1-2',
+        children: [
+          { key: '0-0-1-0', title: 'parent 1-2-0', disableCheckbox: true },
+          { key: '0-0-1-1', title: 'parent 1-2-1' },
+          { key: '0-0-1-2', title: 'parent 1-2-2' },
+          { key: '0-0-1-3', title: 'parent 1-2-3' },
+          { key: '0-0-1-4', title: 'parent 1-2-4' },
+          { key: '0-0-1-5', title: 'parent 1-2-5' },
+          { key: '0-0-1-6', title: 'parent 1-2-6' },
+          { key: '0-0-1-7', title: 'parent 1-2-7' },
+          { key: '0-0-1-8', title: 'parent 1-2-8' },
+          { key: '0-0-1-9', title: 'parent 1-2-9' },
+          { key: 1128, title: 1128 },
+        ],
+      },
+    ],
+  },
+];
+
+const Demo = ()=> {
+    return (
+      <div style={{ margin: '0 20px' }}>
+        <h2>default width</h2>
+        <Tree
+          className="myCls"
+          defaultExpandAll
+          treeData={treeData}
+        />
+
+        <h2>indent width 8</h2>
+        <Tree
+          className="myCls"
+          defaultExpandAll
+          treeData={treeData}
+          indentWidth={8}
+        />
+        <h2>indent width 20</h2>
+        <Tree
+          className="myCls"
+          defaultExpandAll
+          treeData={treeData}
+          indentWidth={20}
+        />
+
+        <h2>indent width 40</h2>
+        <Tree
+          className="myCls"
+          defaultExpandAll
+          treeData={treeData}
+          indentWidth={40}
+        />
+      </div>
+    );
+}
+
+export default Demo;
diff --git a/src/Indent.tsx b/src/Indent.tsx
index 93808c88..80534689 100644
--- a/src/Indent.tsx
+++ b/src/Indent.tsx
@@ -6,9 +6,14 @@ interface IndentProps {
   level: number;
   isStart: boolean[];
   isEnd: boolean[];
+  width?: number
 }
 
-const Indent = ({ prefixCls, level, isStart, isEnd }: IndentProps) => {
+const Indent: React.FC<IndentProps> = ({ prefixCls, level, isStart, isEnd, width }) => {
+  const widthStyle: React.CSSProperties = width >= 0 ? {
+    width: width
+  } : {}
+
   const baseClassName = `${prefixCls}-indent-unit`;
   const list: React.ReactElement[] = [];
   for (let i = 0; i < level; i += 1) {
@@ -19,6 +24,7 @@ const Indent = ({ prefixCls, level, isStart, isEnd }: IndentProps) => {
           [`${baseClassName}-start`]: isStart[i],
           [`${baseClassName}-end`]: isEnd[i],
         })}
+        style={widthStyle}
       />,
     );
   }
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 65bb9391..3712e84c 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -75,6 +75,7 @@ interface NodeListProps<TreeDataType extends BasicDataNode> {
   checkable?: boolean;
   selectable?: boolean;
   disabled?: boolean;
+  indentWidth?: number;
 
   expandedKeys: Key[];
   selectedKeys: Key[];
@@ -149,6 +150,7 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
     halfCheckedKeys,
     keyEntities,
     disabled,
+    indentWidth,
 
     dragging,
     dragOverNodeKey,
@@ -362,6 +364,7 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
               isStart={isStart}
               isEnd={isEnd}
               motion={motion}
+              indentWidth={indentWidth}
               motionNodes={key === MOTION_KEY ? transitionRange : null}
               motionType={motionType}
               onMotionStart={onListChangeStart}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 6c0455e0..d4384bda 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -95,6 +95,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   showLine?: boolean;
   showIcon?: boolean;
   icon?: IconType;
+  indentWidth?: number;
   selectable?: boolean;
   expandAction?: ExpandAction;
   disabled?: boolean;
@@ -1365,6 +1366,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       selectable,
       showIcon,
       icon,
+      indentWidth,
       switcherIcon,
       draggable,
       checkable,
@@ -1423,6 +1425,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
           dropPosition,
           dragOverNodeKey,
           indent,
+          indentWidth,
           direction,
           dropIndicatorRender,
 
@@ -1465,6 +1468,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
             disabled={disabled}
             selectable={selectable}
             checkable={!!checkable}
+            indentWidth={indentWidth}
             motion={motion}
             dragging={draggingNodeKey !== null}
             height={height}
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 20494bdf..bf776ac2 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -521,6 +521,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       data,
       onMouseMove,
       selectable,
+      indentWidth,
       ...otherProps
     } = this.props;
     const {
@@ -581,7 +582,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
         {...ariaSelected}
         {...dataOrAriaAttributeProps}
       >
-        <Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
+        <Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} width={indentWidth} />
         {this.renderDragHandler()}
         {this.renderSwitcher()}
         {this.renderCheckbox()}
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 2132ff26..d9e4db22 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -57,6 +57,7 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
   dropTargetKey: Key | null;
   dropPosition: -1 | 0 | 1 | null;
   indent: number | null;
+  indentWidth?: number;
   dropIndicatorRender: (props: {
     dropPosition: -1 | 0 | 1;
     dropLevelOffset: number;
diff --git a/src/interface.tsx b/src/interface.tsx
index 6c5e5ef4..1ceef23c 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -27,6 +27,7 @@ export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
   isEnd?: boolean[];
   active?: boolean;
   onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
+  indentWidth?: number
 
   // By user
   isLeaf?: boolean;
diff --git a/tests/TreeProps.spec.tsx b/tests/TreeProps.spec.tsx
index ef38052e..0f059757 100644
--- a/tests/TreeProps.spec.tsx
+++ b/tests/TreeProps.spec.tsx
@@ -691,6 +691,18 @@ describe('Tree Props', () => {
     );
   });
 
+  it('indentWidth', () => {
+    const { container } = render(
+        <Tree defaultExpandAll indentWidth={200}>
+          <TreeNode key="0-0" title="parent">
+            <TreeNode key="0-0-0" title="child" />
+          </TreeNode>
+        </Tree>
+    );
+    
+    expect(getComputedStyle(container.querySelector(".rc-tree-indent-unit-start")).width).toBe('200px')
+  });
+
   it('onDoubleClick', () => {
     const onClick = jest.fn();
     const onDoubleClick = jest.fn();
@@ -763,7 +775,7 @@ describe('Tree Props', () => {
           return new FakePromise(ret);
         };
 
-        catch = () => {};
+        catch = () => { };
       }
 
       // eslint-disable-next-line prefer-const

From eeba271f4f9a20884f1e73d8cf06096450e79438 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 19 Feb 2024 16:29:35 +0800
Subject: [PATCH 336/397] chore(deps-dev): bump eslint-plugin-unicorn from
 49.0.0 to 51.0.1 (#805)

Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 49.0.0 to 51.0.1.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v49.0.0...v51.0.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-unicorn
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index db7ff8ec..2ce8dc4c 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
     "dumi": "^2.1.0",
     "eslint": "^8.55.0",
     "eslint-plugin-jest": "^27.6.0",
-    "eslint-plugin-unicorn": "^49.0.0",
+    "eslint-plugin-unicorn": "^51.0.1",
     "father": "^4.4.0",
     "gh-pages": "^6.1.1",
     "glob": "^7.1.6",

From 41a27f1ef63e705af5e4c310b8a43faaa421c872 Mon Sep 17 00:00:00 2001
From: SuperYY <45654909+wkmyws@users.noreply.github.com>
Date: Fri, 23 Feb 2024 21:56:35 +0800
Subject: [PATCH 337/397] type: add type annotate (#806)

* add type annotate

* Update src/utils/treeUtil.ts

Co-authored-by: afc163 <afc163@gmail.com>

---------

Co-authored-by: afc163 <afc163@gmail.com>
---
 src/utils/treeUtil.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index ce42ea7e..2616c477 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -311,7 +311,7 @@ export function convertDataToEntities(
 
   const posEntities = {};
   const keyEntities = {};
-  let wrapper = {
+  let wrapper: Wrapper = {
     posEntities,
     keyEntities,
   };

From d1a57b9bb1bf7599c8c4841431a095f418e5e40d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Tue, 7 May 2024 16:49:51 +0800
Subject: [PATCH 338/397] fix: `onExpand` should check if children tree node
 loaded (#831)

* fix: loadedKeys should not trigger when onExpand

* fix: loadedKeys conflict
---
 src/Tree.tsx             |  9 ++++++-
 src/TreeNode.tsx         | 16 +++++++-----
 tests/TreeProps.spec.tsx | 55 ++++++++++++++++++++++++++++++++--------
 3 files changed, 62 insertions(+), 18 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index d4384bda..4cd0fb82 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -29,7 +29,7 @@ import {
   SafeKey,
   ScrollTo,
 } from './interface';
-import NodeList, { MotionEntity, MOTION_KEY, NodeListRef } from './NodeList';
+import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
 import TreeNode from './TreeNode';
 import {
   arrAdd,
@@ -957,6 +957,13 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
   onNodeLoad = (treeNode: EventDataNode<TreeDataType>) => {
     const { key } = treeNode;
+    const { keyEntities } = this.state;
+
+    // Skip if has children already
+    const entity = getEntity(keyEntities, key);
+    if (entity?.children?.length) {
+      return;
+    }
 
     const loadPromise = new Promise<void>((resolve, reject) => {
       // We need to get the latest state of loading/loaded keys
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index bf776ac2..db9c68b8 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -251,7 +251,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
   };
 
   // Load data to avoid default expanded tree without data
-  syncLoadData = props => {
+  syncLoadData = (props: InternalTreeNodeProps) => {
     const { expanded, loading, loaded } = props;
     const {
       context: { loadData, onNodeLoad },
@@ -262,12 +262,10 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     }
 
     // read from state to avoid loadData at same time
-    if (loadData && expanded && !this.isLeaf()) {
+    if (loadData && expanded && !this.isLeaf() && !loaded) {
       // We needn't reload data when has children in sync logic
       // It's only needed in node expanded
-      if (!this.hasChildren() && !loaded) {
-        onNodeLoad(convertNodePropsToEventData(this.props));
-      }
+      onNodeLoad(convertNodePropsToEventData(this.props));
     }
   };
 
@@ -582,7 +580,13 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
         {...ariaSelected}
         {...dataOrAriaAttributeProps}
       >
-        <Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} width={indentWidth} />
+        <Indent
+          prefixCls={prefixCls}
+          level={level}
+          isStart={isStart}
+          isEnd={isEnd}
+          width={indentWidth}
+        />
         {this.renderDragHandler()}
         {this.renderSwitcher()}
         {this.renderCheckbox()}
diff --git a/tests/TreeProps.spec.tsx b/tests/TreeProps.spec.tsx
index 0f059757..3a00bb1e 100644
--- a/tests/TreeProps.spec.tsx
+++ b/tests/TreeProps.spec.tsx
@@ -559,7 +559,7 @@ describe('Tree Props', () => {
         </Tree>,
       );
 
-      fireEvent.click(container.querySelector('.rc-tree-switcher'));
+      fireEvent.click(container.querySelector('.rc-tree-switcher')!);
 
       expect(onExpand).toHaveBeenCalled();
 
@@ -614,14 +614,14 @@ describe('Tree Props', () => {
       const { container } = render(<Test />);
 
       // Parent click
-      fireEvent.click(container.querySelector('.rc-tree-switcher'));
+      fireEvent.click(container.querySelector('.rc-tree-switcher')!);
 
       setTimeout(() => {
         // Child click
         fireEvent.click(container.querySelectorAll('.rc-tree-switcher')[1]);
 
         setTimeout(() => {
-          expect(count).toBe(2);
+          expect(count).toBe(1);
           done();
         }, 500);
       }, 500);
@@ -654,6 +654,37 @@ describe('Tree Props', () => {
         'Warning: Retry for `loadData` many times but still failed. No more retry.',
       );
     });
+
+    // https://github.com/ant-design/ant-design/issues/48796
+    it('skip load if has children', () => {
+      const loadData = jest.fn(async (info) => {
+        console.log('->', info.key);
+      });
+
+      const { container } = render(
+        <Tree
+          loadedKeys={[]}
+          loadData={loadData}
+          treeData={[
+            {
+              title: 'parent',
+              key: 'parent',
+              isLeaf: false,
+              children: [
+                {
+                  title: 'child',
+                  key: 'child',
+                  isLeaf: true,
+                },
+              ],
+            },
+          ]}
+        />,
+      );
+
+      fireEvent.click(container.querySelector('.rc-tree-switcher')!);
+      expect(loadData).not.toHaveBeenCalled();
+    });
   });
 
   it('icon', () => {
@@ -693,14 +724,16 @@ describe('Tree Props', () => {
 
   it('indentWidth', () => {
     const { container } = render(
-        <Tree defaultExpandAll indentWidth={200}>
-          <TreeNode key="0-0" title="parent">
-            <TreeNode key="0-0-0" title="child" />
-          </TreeNode>
-        </Tree>
+      <Tree defaultExpandAll indentWidth={200}>
+        <TreeNode key="0-0" title="parent">
+          <TreeNode key="0-0-0" title="child" />
+        </TreeNode>
+      </Tree>,
+    );
+
+    expect(getComputedStyle(container.querySelector('.rc-tree-indent-unit-start')).width).toBe(
+      '200px',
     );
-    
-    expect(getComputedStyle(container.querySelector(".rc-tree-indent-unit-start")).width).toBe('200px')
   });
 
   it('onDoubleClick', () => {
@@ -775,7 +808,7 @@ describe('Tree Props', () => {
           return new FakePromise(ret);
         };
 
-        catch = () => { };
+        catch = () => {};
       }
 
       // eslint-disable-next-line prefer-const

From 5ad456b16872a496107092a52915b86a161a5401 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 7 May 2024 16:51:16 +0800
Subject: [PATCH 339/397] 5.8.6

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 2ce8dc4c..8e086028 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.8.5",
+  "version": "5.8.6",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 5e3f9d1128eee2c0556804bae9f2f3587209528a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Tue, 7 May 2024 17:14:42 +0800
Subject: [PATCH 340/397] revert: indentWidth prop (#832)

* revert: indentWidth prop

* chore: clean up
---
 docs/demo/custom-indent-width.md      |  8 ---
 docs/examples/custom-indent-width.jsx | 70 ---------------------------
 src/Indent.tsx                        | 10 +---
 src/NodeList.tsx                      |  3 --
 src/Tree.tsx                          |  4 --
 src/TreeNode.tsx                      |  2 -
 src/contextTypes.ts                   |  1 -
 src/interface.tsx                     |  1 -
 tests/TreeProps.spec.tsx              | 16 +-----
 9 files changed, 3 insertions(+), 112 deletions(-)
 delete mode 100644 docs/demo/custom-indent-width.md
 delete mode 100644 docs/examples/custom-indent-width.jsx

diff --git a/docs/demo/custom-indent-width.md b/docs/demo/custom-indent-width.md
deleted file mode 100644
index 6f47a8e4..00000000
--- a/docs/demo/custom-indent-width.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: Custom Indent Width
-nav:
-  title: Demo
-  path: /demo
----
-
-<code src="../examples/custom-indent-width.jsx"></code>
diff --git a/docs/examples/custom-indent-width.jsx b/docs/examples/custom-indent-width.jsx
deleted file mode 100644
index dd31a5cd..00000000
--- a/docs/examples/custom-indent-width.jsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/* eslint-disable no-alert, no-console, react/no-find-dom-node */
-import React from 'react';
-import '../../assets/index.less';
-import './basic.less';
-import Tree from 'rc-tree';
-
-const treeData = [
-  {
-    key: '0-0',
-    title: 'parent 1',
-    children: [
-      { key: '0-0-0', title: 'parent 1-1', children: [{ key: '0-0-0-0', title: 'parent 1-1-0' }] },
-      {
-        key: '0-0-1',
-        title: 'parent 1-2',
-        children: [
-          { key: '0-0-1-0', title: 'parent 1-2-0', disableCheckbox: true },
-          { key: '0-0-1-1', title: 'parent 1-2-1' },
-          { key: '0-0-1-2', title: 'parent 1-2-2' },
-          { key: '0-0-1-3', title: 'parent 1-2-3' },
-          { key: '0-0-1-4', title: 'parent 1-2-4' },
-          { key: '0-0-1-5', title: 'parent 1-2-5' },
-          { key: '0-0-1-6', title: 'parent 1-2-6' },
-          { key: '0-0-1-7', title: 'parent 1-2-7' },
-          { key: '0-0-1-8', title: 'parent 1-2-8' },
-          { key: '0-0-1-9', title: 'parent 1-2-9' },
-          { key: 1128, title: 1128 },
-        ],
-      },
-    ],
-  },
-];
-
-const Demo = ()=> {
-    return (
-      <div style={{ margin: '0 20px' }}>
-        <h2>default width</h2>
-        <Tree
-          className="myCls"
-          defaultExpandAll
-          treeData={treeData}
-        />
-
-        <h2>indent width 8</h2>
-        <Tree
-          className="myCls"
-          defaultExpandAll
-          treeData={treeData}
-          indentWidth={8}
-        />
-        <h2>indent width 20</h2>
-        <Tree
-          className="myCls"
-          defaultExpandAll
-          treeData={treeData}
-          indentWidth={20}
-        />
-
-        <h2>indent width 40</h2>
-        <Tree
-          className="myCls"
-          defaultExpandAll
-          treeData={treeData}
-          indentWidth={40}
-        />
-      </div>
-    );
-}
-
-export default Demo;
diff --git a/src/Indent.tsx b/src/Indent.tsx
index 80534689..41aac59d 100644
--- a/src/Indent.tsx
+++ b/src/Indent.tsx
@@ -1,19 +1,14 @@
-import * as React from 'react';
 import classNames from 'classnames';
+import * as React from 'react';
 
 interface IndentProps {
   prefixCls: string;
   level: number;
   isStart: boolean[];
   isEnd: boolean[];
-  width?: number
 }
 
-const Indent: React.FC<IndentProps> = ({ prefixCls, level, isStart, isEnd, width }) => {
-  const widthStyle: React.CSSProperties = width >= 0 ? {
-    width: width
-  } : {}
-
+const Indent: React.FC<IndentProps> = ({ prefixCls, level, isStart, isEnd }) => {
   const baseClassName = `${prefixCls}-indent-unit`;
   const list: React.ReactElement[] = [];
   for (let i = 0; i < level; i += 1) {
@@ -24,7 +19,6 @@ const Indent: React.FC<IndentProps> = ({ prefixCls, level, isStart, isEnd, width
           [`${baseClassName}-start`]: isStart[i],
           [`${baseClassName}-end`]: isEnd[i],
         })}
-        style={widthStyle}
       />,
     );
   }
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 3712e84c..65bb9391 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -75,7 +75,6 @@ interface NodeListProps<TreeDataType extends BasicDataNode> {
   checkable?: boolean;
   selectable?: boolean;
   disabled?: boolean;
-  indentWidth?: number;
 
   expandedKeys: Key[];
   selectedKeys: Key[];
@@ -150,7 +149,6 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
     halfCheckedKeys,
     keyEntities,
     disabled,
-    indentWidth,
 
     dragging,
     dragOverNodeKey,
@@ -364,7 +362,6 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
               isStart={isStart}
               isEnd={isEnd}
               motion={motion}
-              indentWidth={indentWidth}
               motionNodes={key === MOTION_KEY ? transitionRange : null}
               motionType={motionType}
               onMotionStart={onListChangeStart}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 4cd0fb82..690566e9 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -95,7 +95,6 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   showLine?: boolean;
   showIcon?: boolean;
   icon?: IconType;
-  indentWidth?: number;
   selectable?: boolean;
   expandAction?: ExpandAction;
   disabled?: boolean;
@@ -1373,7 +1372,6 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       selectable,
       showIcon,
       icon,
-      indentWidth,
       switcherIcon,
       draggable,
       checkable,
@@ -1432,7 +1430,6 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
           dropPosition,
           dragOverNodeKey,
           indent,
-          indentWidth,
           direction,
           dropIndicatorRender,
 
@@ -1475,7 +1472,6 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
             disabled={disabled}
             selectable={selectable}
             checkable={!!checkable}
-            indentWidth={indentWidth}
             motion={motion}
             dragging={draggingNodeKey !== null}
             height={height}
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index db9c68b8..3a51f2cb 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -519,7 +519,6 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
       data,
       onMouseMove,
       selectable,
-      indentWidth,
       ...otherProps
     } = this.props;
     const {
@@ -585,7 +584,6 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           level={level}
           isStart={isStart}
           isEnd={isEnd}
-          width={indentWidth}
         />
         {this.renderDragHandler()}
         {this.renderSwitcher()}
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index d9e4db22..2132ff26 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -57,7 +57,6 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
   dropTargetKey: Key | null;
   dropPosition: -1 | 0 | 1 | null;
   indent: number | null;
-  indentWidth?: number;
   dropIndicatorRender: (props: {
     dropPosition: -1 | 0 | 1;
     dropLevelOffset: number;
diff --git a/src/interface.tsx b/src/interface.tsx
index 1ceef23c..6c5e5ef4 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -27,7 +27,6 @@ export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
   isEnd?: boolean[];
   active?: boolean;
   onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
-  indentWidth?: number
 
   // By user
   isLeaf?: boolean;
diff --git a/tests/TreeProps.spec.tsx b/tests/TreeProps.spec.tsx
index 3a00bb1e..d6b1038f 100644
--- a/tests/TreeProps.spec.tsx
+++ b/tests/TreeProps.spec.tsx
@@ -657,7 +657,7 @@ describe('Tree Props', () => {
 
     // https://github.com/ant-design/ant-design/issues/48796
     it('skip load if has children', () => {
-      const loadData = jest.fn(async (info) => {
+      const loadData = jest.fn(async info => {
         console.log('->', info.key);
       });
 
@@ -722,20 +722,6 @@ describe('Tree Props', () => {
     );
   });
 
-  it('indentWidth', () => {
-    const { container } = render(
-      <Tree defaultExpandAll indentWidth={200}>
-        <TreeNode key="0-0" title="parent">
-          <TreeNode key="0-0-0" title="child" />
-        </TreeNode>
-      </Tree>,
-    );
-
-    expect(getComputedStyle(container.querySelector('.rc-tree-indent-unit-start')).width).toBe(
-      '200px',
-    );
-  });
-
   it('onDoubleClick', () => {
     const onClick = jest.fn();
     const onDoubleClick = jest.fn();

From 757e6f0dc64c9610006909664027252456c7b821 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 7 May 2024 17:15:49 +0800
Subject: [PATCH 341/397] 5.8.7

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 8e086028..25874871 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.8.6",
+  "version": "5.8.7",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 33c38ad16f79fbc5db27c571466b38b46ca8e096 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Wed, 12 Jun 2024 15:02:05 +0800
Subject: [PATCH 342/397] enhance: perf of babel (#842)

---
 src/utils/treeUtil.ts | 26 ++++++++++++++------------
 1 file changed, 14 insertions(+), 12 deletions(-)

diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 2616c477..701c1b87 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -148,18 +148,20 @@ export function flattenTreeData<TreeDataType extends BasicDataNode = DataNode>(
       }
 
       // Add FlattenDataNode into list
-      const flattenNode: FlattenNode<TreeDataType> = {
-        ...omit(treeNode, [...fieldTitles, fieldKey, fieldChildren] as any),
-        title: mergedTitle,
-        key: mergedKey,
-        parent,
-        pos,
-        children: null,
-        data: treeNode,
-        isStart: [...(parent ? parent.isStart : []), index === 0],
-        isEnd: [...(parent ? parent.isEnd : []), index === list.length - 1],
-      };
-
+      // We use `Object.assign` here to save perf since babel's `objectSpread` has perf issue
+      const flattenNode: FlattenNode<TreeDataType> = Object.assign(
+        omit(treeNode, [...fieldTitles, fieldKey, fieldChildren] as any),
+        {
+          title: mergedTitle,
+          key: mergedKey,
+          parent,
+          pos,
+          children: null,
+          data: treeNode,
+          isStart: [...(parent ? parent.isStart : []), index === 0],
+          isEnd: [...(parent ? parent.isEnd : []), index === list.length - 1],
+        },
+      );
       flattenList.push(flattenNode);
 
       // Loop treeNode children

From f4e723b2b9c41a327235d178bdb603f21311c92d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Wed, 12 Jun 2024 15:04:04 +0800
Subject: [PATCH 343/397] 5.8.8

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 25874871..7805ce49 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.8.7",
+  "version": "5.8.8",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From b48eef3911bdaa5b6938727aa98b6f5d2ba47d39 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 8 Jul 2024 05:55:22 +0800
Subject: [PATCH 344/397] chore(deps-dev): bump @testing-library/react from
 14.3.1 to 15.0.7 (#833)

Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 14.3.1 to 15.0.7.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v14.3.1...v15.0.7)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 7805ce49..9e293d52 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
   "devDependencies": {
     "@rc-component/father-plugin": "^1.0.2",
     "@testing-library/jest-dom": "^6.1.5",
-    "@testing-library/react": "^14.1.2",
+    "@testing-library/react": "^15.0.7",
     "@types/jest": "^29.5.10",
     "@types/react": "^18.0.0",
     "@types/react-dom": "^18.2.17",

From 8d0dc51512e3f49571cdd80e9a84368d7911c963 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 8 Jul 2024 05:55:35 +0800
Subject: [PATCH 345/397] chore(deps-dev): bump eslint-plugin-unicorn from
 51.0.1 to 53.0.0 (#835)

Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 51.0.1 to 53.0.0.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v51.0.1...v53.0.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-unicorn
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 9e293d52..5affb54f 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
     "dumi": "^2.1.0",
     "eslint": "^8.55.0",
     "eslint-plugin-jest": "^27.6.0",
-    "eslint-plugin-unicorn": "^51.0.1",
+    "eslint-plugin-unicorn": "^53.0.0",
     "father": "^4.4.0",
     "gh-pages": "^6.1.1",
     "glob": "^7.1.6",

From 3edf92fb486a9347324cdd6335723b8531ffa6e9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 8 Jul 2024 05:55:47 +0800
Subject: [PATCH 346/397] chore(deps-dev): bump np from 9.2.0 to 10.0.5 (#823)

Bumps [np](https://github.com/sindresorhus/np) from 9.2.0 to 10.0.5.
- [Release notes](https://github.com/sindresorhus/np/releases)
- [Commits](https://github.com/sindresorhus/np/compare/v9.2.0...v10.0.5)

---
updated-dependencies:
- dependency-name: np
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 5affb54f..ba7ed751 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,7 @@
     "gh-pages": "^6.1.1",
     "glob": "^7.1.6",
     "less": "^3.11.1",
-    "np": "^9.0.0",
+    "np": "^10.0.5",
     "rc-dialog": "^8.1.0",
     "rc-test": "^7.0.15",
     "rc-tooltip": "5.x",

From fe8ef417989a797cdaaeff41ee0eb045207fc982 Mon Sep 17 00:00:00 2001
From: Avan <layouwen@gmail.com>
Date: Tue, 13 Aug 2024 18:21:04 +0800
Subject: [PATCH 347/397] test: use codecov/codecov-action (#860)

---
 .github/workflows/main.yml | 46 ++++++++++++++++++++------------------
 1 file changed, 24 insertions(+), 22 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 7d336a09..2097f2a7 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,30 +1,26 @@
 name: CI
 
-on:
-  push:
-    branches: [master]
-  pull_request:
-    branches: [master]
+on: ['push', 'pull_request']
 
 jobs:
   setup:
     runs-on: ubuntu-latest
     steps:
       - name: checkout
-        uses: actions/checkout@master
+        uses: actions/checkout@v4
 
-      - uses: actions/setup-node@v1
+      - uses: actions/setup-node@v4
         with:
-          node-version: '18'
+          node-version: '20'
 
       - name: cache package-lock.json
-        uses: actions/cache@v2
+        uses: actions/cache@v4
         with:
           path: package-temp-dir
           key: lock-${{ github.sha }}
 
       - name: create package-lock.json
-        run: npm i --package-lock-only
+        run: npm i --package-lock-only --ignore-scripts
 
       - name: hack for singe file
         run: |
@@ -32,9 +28,10 @@ jobs:
             mkdir package-temp-dir
           fi
           cp package-lock.json package-temp-dir
+
       - name: cache node_modules
         id: node_modules_cache_id
-        uses: actions/cache@v2
+        uses: actions/cache@v4
         with:
           path: node_modules
           key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
@@ -47,16 +44,16 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: checkout
-        uses: actions/checkout@master
+        uses: actions/checkout@v4
 
       - name: restore cache from package-lock.json
-        uses: actions/cache@v2
+        uses: actions/cache@v4
         with:
           path: package-temp-dir
           key: lock-${{ github.sha }}
 
       - name: restore cache from node_modules
-        uses: actions/cache@v2
+        uses: actions/cache@v4
         with:
           path: node_modules
           key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
@@ -70,16 +67,16 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: checkout
-        uses: actions/checkout@master
+        uses: actions/checkout@v4
 
       - name: restore cache from package-lock.json
-        uses: actions/cache@v2
+        uses: actions/cache@v4
         with:
           path: package-temp-dir
           key: lock-${{ github.sha }}
 
       - name: restore cache from node_modules
-        uses: actions/cache@v2
+        uses: actions/cache@v4
         with:
           path: node_modules
           key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
@@ -93,21 +90,26 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: checkout
-        uses: actions/checkout@master
+        uses: actions/checkout@v4
 
       - name: restore cache from package-lock.json
-        uses: actions/cache@v2
+        uses: actions/cache@v4
         with:
           path: package-temp-dir
           key: lock-${{ github.sha }}
 
       - name: restore cache from node_modules
-        uses: actions/cache@v2
+        uses: actions/cache@v4
         with:
           path: node_modules
           key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
 
       - name: coverage
-        run: npm test -- --coverage && bash <(curl -s https://codecov.io/bash)
+        run: npm test -- --coverage
+
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v4
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
 
-    needs: setup
\ No newline at end of file
+    needs: setup

From de8dd149defe8706fb4d7f22a72db9fdb0aba223 Mon Sep 17 00:00:00 2001
From: Avan <layouwen@gmail.com>
Date: Tue, 20 Aug 2024 02:24:58 +0800
Subject: [PATCH 348/397] test: use unified github action yml (#861)

---
 .github/workflows/main.yml | 119 ++-----------------------------------
 1 file changed, 5 insertions(+), 114 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 2097f2a7..f860ff10 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,115 +1,6 @@
-name: CI
-
-on: ['push', 'pull_request']
-
+name: ✅ test
+on: [push, pull_request]
 jobs:
-  setup:
-    runs-on: ubuntu-latest
-    steps:
-      - name: checkout
-        uses: actions/checkout@v4
-
-      - uses: actions/setup-node@v4
-        with:
-          node-version: '20'
-
-      - name: cache package-lock.json
-        uses: actions/cache@v4
-        with:
-          path: package-temp-dir
-          key: lock-${{ github.sha }}
-
-      - name: create package-lock.json
-        run: npm i --package-lock-only --ignore-scripts
-
-      - name: hack for singe file
-        run: |
-          if [ ! -d "package-temp-dir" ]; then
-            mkdir package-temp-dir
-          fi
-          cp package-lock.json package-temp-dir
-
-      - name: cache node_modules
-        id: node_modules_cache_id
-        uses: actions/cache@v4
-        with:
-          path: node_modules
-          key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
-
-      - name: install
-        if: steps.node_modules_cache_id.outputs.cache-hit != 'true'
-        run: npm ci
-
-  lint:
-    runs-on: ubuntu-latest
-    steps:
-      - name: checkout
-        uses: actions/checkout@v4
-
-      - name: restore cache from package-lock.json
-        uses: actions/cache@v4
-        with:
-          path: package-temp-dir
-          key: lock-${{ github.sha }}
-
-      - name: restore cache from node_modules
-        uses: actions/cache@v4
-        with:
-          path: node_modules
-          key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
-
-      - name: lint
-        run: npm run lint
-
-    needs: setup
-
-  compile:
-    runs-on: ubuntu-latest
-    steps:
-      - name: checkout
-        uses: actions/checkout@v4
-
-      - name: restore cache from package-lock.json
-        uses: actions/cache@v4
-        with:
-          path: package-temp-dir
-          key: lock-${{ github.sha }}
-
-      - name: restore cache from node_modules
-        uses: actions/cache@v4
-        with:
-          path: node_modules
-          key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
-
-      - name: compile
-        run: npm run compile
-
-    needs: setup
-
-  coverage:
-    runs-on: ubuntu-latest
-    steps:
-      - name: checkout
-        uses: actions/checkout@v4
-
-      - name: restore cache from package-lock.json
-        uses: actions/cache@v4
-        with:
-          path: package-temp-dir
-          key: lock-${{ github.sha }}
-
-      - name: restore cache from node_modules
-        uses: actions/cache@v4
-        with:
-          path: node_modules
-          key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }}
-
-      - name: coverage
-        run: npm test -- --coverage
-
-      - name: Upload coverage to Codecov
-        uses: codecov/codecov-action@v4
-        with:
-          token: ${{ secrets.CODECOV_TOKEN }}
-
-    needs: setup
+  test:
+    uses: react-component/rc-test/.github/workflows/test.yml@main
+    secrets: inherit

From fa401b763664e48ad529ce336794a7bd68e37a77 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Tue, 3 Sep 2024 15:07:01 +0800
Subject: [PATCH 349/397] perf: enhance virtual Tree perf (#867)

* perf: skip large check

* perf: expanded keys filter

* perf: expanded keys filter before

* test: update snapshot

* fix: key match

* test: fix test case
---
 src/NodeList.tsx                              | 14 +++----
 src/Tree.tsx                                  | 12 +++++-
 tests/Accessibility.spec.tsx                  |  6 +--
 tests/Tree.spec.tsx                           |  2 +-
 tests/TreeDraggable.spec.tsx                  |  3 +-
 tests/__snapshots__/Tree.spec.tsx.snap        | 20 ++++-----
 .../__snapshots__/TreeNodeProps.spec.tsx.snap | 32 +++++++-------
 tests/__snapshots__/TreeProps.spec.tsx.snap   | 42 +++++++++----------
 tsconfig.json                                 |  5 ++-
 9 files changed, 74 insertions(+), 62 deletions(-)

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 65bb9391..f3e18cb9 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -5,6 +5,7 @@
 import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
 import VirtualList, { ListRef } from 'rc-virtual-list';
 import * as React from 'react';
+import MotionTreeNode from './MotionTreeNode';
 import {
   BasicDataNode,
   DataEntity,
@@ -14,7 +15,6 @@ import {
   KeyEntities,
   ScrollTo,
 } from './interface';
-import MotionTreeNode from './MotionTreeNode';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
 import { getKey, getTreeNodeProps } from './utils/treeUtil';
 
@@ -326,12 +326,12 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
         itemHeight={itemHeight}
         prefixCls={`${prefixCls}-list`}
         ref={listRef}
-        onVisibleChange={(originList, fullList) => {
-          const originSet = new Set(originList);
-          const restList = fullList.filter(item => !originSet.has(item));
-
-          // Motion node is not render. Skip motion
-          if (restList.some(item => itemKey(item) === MOTION_KEY)) {
+        onVisibleChange={originList => {
+          // The best match is using `fullList` - `originList` = `restList`
+          // and check the `restList` to see if has the MOTION_KEY node
+          // but this will cause performance issue for long list compare
+          // we just check `originList` and repeat trigger `onMotionEnd`
+          if (originList.every(item => itemKey(item) !== MOTION_KEY)) {
             onMotionEnd();
           }
         }}
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 690566e9..a74a084e 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -397,7 +397,17 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     } else if (!prevProps && props.defaultExpandAll) {
       const cloneKeyEntities = { ...keyEntities };
       delete cloneKeyEntities[MOTION_KEY];
-      newState.expandedKeys = Object.keys(cloneKeyEntities).map(key => cloneKeyEntities[key].key);
+
+      // Only take the key who has the children to enhance the performance
+      const nextExpandedKeys: React.Key[] = [];
+      Object.keys(cloneKeyEntities).forEach(key => {
+        const entity = cloneKeyEntities[key];
+        if (entity.children && entity.children.length) {
+          nextExpandedKeys.push(entity.key);
+        }
+      });
+
+      newState.expandedKeys = nextExpandedKeys;
     } else if (!prevProps && props.defaultExpandedKeys) {
       newState.expandedKeys =
         props.autoExpandParent || props.defaultExpandParent
diff --git a/tests/Accessibility.spec.tsx b/tests/Accessibility.spec.tsx
index 44c86fee..a66fff7e 100644
--- a/tests/Accessibility.spec.tsx
+++ b/tests/Accessibility.spec.tsx
@@ -1,7 +1,7 @@
 /* eslint-disable no-undef, react/no-multi-comp */
-import React from 'react';
+import { fireEvent, render } from '@testing-library/react';
 import KeyCode from 'rc-util/lib/KeyCode';
-import { render, fireEvent } from '@testing-library/react';
+import React from 'react';
 import Tree, { FieldDataNode } from '../src';
 import { spyConsole } from './util';
 
@@ -34,7 +34,7 @@ describe('Tree Accessibility', () => {
           onBlur={onBlur}
           onKeyDown={onKeyDown}
           onActiveChange={onActiveChange}
-          defaultExpandAll
+          defaultExpandedKeys={['parent', 'child 1', 'child 2']}
           treeData={[{ key: 'parent', children: [{ key: 'child 1' }, { key: 'child 2' }] }]}
         />,
       );
diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index f0b70078..d59fc31b 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -371,7 +371,7 @@ describe('Tree Basic', () => {
     // https://github.com/react-component/tree/issues/117
     it('check works correctly after dragging children under another node', () => {
       const renderTree = (children: React.ReactNode) => (
-        <Tree defaultExpandAll checkable>
+        <Tree defaultExpandedKeys={['0-0', '0-0-1']} checkable>
           {children}
         </Tree>
       );
diff --git a/tests/TreeDraggable.spec.tsx b/tests/TreeDraggable.spec.tsx
index 2d5737cf..d8876fd8 100644
--- a/tests/TreeDraggable.spec.tsx
+++ b/tests/TreeDraggable.spec.tsx
@@ -2,6 +2,7 @@
 react/no-unused-state, react/prop-types, no-return-assign */
 import { act, createEvent, fireEvent, render } from '@testing-library/react';
 import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
+import React from 'react';
 import Tree, { FieldDataNode, TreeNode, TreeProps } from '../src';
 import { spyConsole } from './util';
 
@@ -67,7 +68,7 @@ describe('Tree Draggable', () => {
     });
     const event = onDragEnter.mock.calls[0][0];
     expect(event.node.key).toEqual('0-0-0-1');
-    expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
+    expect(event.expandedKeys).toEqual(['0-0']);
     expect(onDragEnter).toHaveBeenCalledTimes(1);
   });
 
diff --git a/tests/__snapshots__/Tree.spec.tsx.snap b/tests/__snapshots__/Tree.spec.tsx.snap
index edf8c435..a26bd869 100644
--- a/tests/__snapshots__/Tree.spec.tsx.snap
+++ b/tests/__snapshots__/Tree.spec.tsx.snap
@@ -69,7 +69,7 @@ exports[`Tree Basic check basic render 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -102,7 +102,7 @@ exports[`Tree Basic check basic render 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -361,7 +361,7 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -387,7 +387,7 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -458,7 +458,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -510,7 +510,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -540,7 +540,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -677,7 +677,7 @@ exports[`Tree Basic renders correctly 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -713,7 +713,7 @@ exports[`Tree Basic renders correctly 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -749,7 +749,7 @@ exports[`Tree Basic renders correctly 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
diff --git a/tests/__snapshots__/TreeNodeProps.spec.tsx.snap b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
index 72492e59..b7b5ee63 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
@@ -96,7 +96,7 @@ exports[`TreeNode Props className 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -129,7 +129,7 @@ exports[`TreeNode Props className 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -159,7 +159,7 @@ exports[`TreeNode Props className 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -230,7 +230,7 @@ exports[`TreeNode Props customize icon component 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -305,7 +305,7 @@ exports[`TreeNode Props customize icon element 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -380,7 +380,7 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -507,7 +507,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
           <div
             aria-grabbed="false"
             aria-label="0-0-0-0"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -541,7 +541,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
           <div
             aria-grabbed="false"
             aria-label="0-0-1"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -572,7 +572,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
           <div
             aria-grabbed="false"
             aria-label="0-1"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -701,7 +701,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             data-test="0-0-0-0"
             draggable="false"
           >
@@ -735,7 +735,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             data-test="0-0-1"
             draggable="false"
           >
@@ -766,7 +766,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             data-test="0-1"
             draggable="false"
           >
@@ -1060,7 +1060,7 @@ exports[`TreeNode Props isLeaf 3`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -1072,14 +1072,14 @@ exports[`TreeNode Props isLeaf 3`] = `
               />
             </span>
             <span
-              class="rc-tree-switcher rc-tree-switcher_open"
+              class="rc-tree-switcher rc-tree-switcher_close"
             />
             <span
-              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
+              class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-close"
               title="---"
             >
               <span
-                class="rc-tree-iconEle rc-tree-icon__open"
+                class="rc-tree-iconEle rc-tree-icon__close"
               />
               <span
                 class="rc-tree-title"
diff --git a/tests/__snapshots__/TreeProps.spec.tsx.snap b/tests/__snapshots__/TreeProps.spec.tsx.snap
index 8a493b6e..9353689e 100644
--- a/tests/__snapshots__/TreeProps.spec.tsx.snap
+++ b/tests/__snapshots__/TreeProps.spec.tsx.snap
@@ -361,7 +361,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -417,7 +417,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -447,7 +447,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -522,7 +522,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -552,7 +552,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -608,7 +608,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -642,7 +642,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -676,7 +676,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -867,7 +867,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -943,7 +943,7 @@ exports[`Tree Props disabled basic 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -1015,7 +1015,7 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -1116,7 +1116,7 @@ exports[`Tree Props icon 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -1224,7 +1224,7 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -1331,7 +1331,7 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -1530,7 +1530,7 @@ exports[`Tree Props multiple 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -2321,7 +2321,7 @@ exports[`Tree Props treeData 1`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -2373,7 +2373,7 @@ exports[`Tree Props treeData 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -2433,7 +2433,7 @@ exports[`Tree Props treeData 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
           >
             <span
@@ -2466,7 +2466,7 @@ exports[`Tree Props treeData 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -2499,7 +2499,7 @@ exports[`Tree Props treeData 1`] = `
           </div>
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
@@ -2574,7 +2574,7 @@ exports[`Tree Props treeData 2`] = `
         >
           <div
             aria-grabbed="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
           >
             <span
diff --git a/tsconfig.json b/tsconfig.json
index 085f4904..fe2e24f4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -3,7 +3,7 @@
     "target": "esnext",
     "moduleResolution": "node",
     "baseUrl": "./",
-    "jsx": "preserve",
+    "jsx": "react",
     "declaration": true,
     "skipLibCheck": true,
     "esModuleInterop": true,
@@ -23,6 +23,7 @@
     ".dumirc.ts",
     "./src/**/*.ts",
     "./src/**/*.tsx",
-    "./docs/**/*.tsx"
+    "./docs/**/*.tsx",
+    "./tests/**/*.tsx",
   ]
 }
\ No newline at end of file

From 0e908dfef20cee002d31f9f74582073152bb6c7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 3 Sep 2024 15:48:05 +0800
Subject: [PATCH 350/397] 5.9.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index ba7ed751..3fe945f9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.8.8",
+  "version": "5.9.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From c43684d55e86e588709e4d02ba6ab41abdaf5815 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 28 Sep 2024 09:06:51 +0800
Subject: [PATCH 351/397] chore(deps-dev): bump eslint-plugin-jest from 27.9.0
 to 28.8.3 (#868)

Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 27.9.0 to 28.8.3.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v27.9.0...v28.8.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-jest
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 3fe945f9..101e2fbe 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
     "@umijs/fabric": "^4.0.1",
     "dumi": "^2.1.0",
     "eslint": "^8.55.0",
-    "eslint-plugin-jest": "^27.6.0",
+    "eslint-plugin-jest": "^28.8.3",
     "eslint-plugin-unicorn": "^53.0.0",
     "father": "^4.4.0",
     "gh-pages": "^6.1.1",

From 00dc70b20ed0905c69ed7286910eddeb86093ca6 Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Sat, 28 Sep 2024 09:09:11 +0800
Subject: [PATCH 352/397] fix: add @types/node (#873)

---
 package.json | 1 +
 1 file changed, 1 insertion(+)

diff --git a/package.json b/package.json
index 101e2fbe..7961d2de 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
     "@testing-library/jest-dom": "^6.1.5",
     "@testing-library/react": "^15.0.7",
     "@types/jest": "^29.5.10",
+    "@types/node": "^22.7.3",
     "@types/react": "^18.0.0",
     "@types/react-dom": "^18.2.17",
     "@types/warning": "^3.0.0",

From 039f3f87743ef38464272799b6fa8059a869e8b6 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 14 Oct 2024 15:10:46 +0800
Subject: [PATCH 353/397] Update README.md

---
 README.md | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 57ad6046..d56dd750 100644
--- a/README.md
+++ b/README.md
@@ -2,14 +2,19 @@
 
 Tree component.
 
-[![NPM version][npm-image]][npm-url] [![dumi](https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square)](https://github.com/umijs/dumi) [![build status][github-actions-image]][github-actions-url] [![Test coverage][codecov-image]][codecov-url] [![Dependencies][david-image]][david-url] [![DevDependencies][david-dev-image]][david-dev-url] [![npm download][download-image]][download-url] [![bundle size][bundlephobia-image]][bundlephobia-url]
+[![NPM version][npm-image]][npm-url]
+[![npm download][download-image]][download-url]
+[![build status][github-actions-image]][github-actions-url]
+[![Codecov][codecov-image]][codecov-url]
+[![bundle size][bundlephobia-image]][bundlephobia-url]
+[![dumi][dumi-image]][dumi-url]
 
 [npm-image]: http://img.shields.io/npm/v/rc-tree.svg?style=flat-square
 [npm-url]: http://npmjs.org/package/rc-tree
-[github-actions-image]: https://github.com/react-component/tree/workflows/CI/badge.svg
-[github-actions-url]: https://github.com/react-component/tree/actions
+[github-actions-image]: https://github.com/react-component/tree/actions/workflows/main.yml/badge.svg
+[github-actions-url]: https://github.com/react-component/tree/actions/workflows/main.yml
 [codecov-image]: https://img.shields.io/codecov/c/github/react-component/tree/master.svg?style=flat-square
-[codecov-url]: https://codecov.io/gh/react-component/tree/branch/master
+[codecov-url]: https://codecov.io/gh/react-component/tree/
 [david-url]: https://david-dm.org/react-component/tree
 [david-image]: https://david-dm.org/react-component/tree/status.svg?style=flat-square
 [david-dev-url]: https://david-dm.org/react-component/tree?type=dev
@@ -18,6 +23,8 @@ Tree component.
 [download-url]: https://npmjs.org/package/rc-tree
 [bundlephobia-url]: https://bundlephobia.com/result?p=rc-tree
 [bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-tree
+[dumi-url]: https://github.com/umijs/dumi
+[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
 
 ## Screenshots
 

From 067f1ab53f9796b713db6d060f0357e026600332 Mon Sep 17 00:00:00 2001
From: flyFatSeal <38580334+flyFatSeal@users.noreply.github.com>
Date: Mon, 14 Oct 2024 15:37:25 +0800
Subject: [PATCH 354/397] refactor: Replace the "in" operator with
 hasOwnProperty. (#876)

Co-authored-by: wangjian <wangjian@xunyou.com>
---
 src/Tree.tsx | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index a74a084e..f8d426ef 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -350,7 +350,9 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     };
 
     function needSync(name: string) {
-      return (!prevProps && name in props) || (prevProps && prevProps[name] !== props[name]);
+      return (
+        (!prevProps && props.hasOwnProperty(name)) || (prevProps && prevProps[name] !== props[name])
+      );
     }
 
     // ================== Tree Node ==================
@@ -579,7 +581,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
           newExpandedKeys = arrAdd(expandedKeys, node.props.eventKey);
         }
 
-        if (!('expandedKeys' in this.props)) {
+        if (!this.props.hasOwnProperty('expandedKeys')) {
           this.setExpandedKeys(newExpandedKeys);
         }
 
@@ -1336,7 +1338,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       const newState = {};
 
       Object.keys(state).forEach(name => {
-        if (name in this.props) {
+        if (this.props.hasOwnProperty(name)) {
           allPassed = false;
           return;
         }

From eadf23d51a20636f3beba7e57adf6a5462c90c33 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 14 Oct 2024 15:39:52 +0800
Subject: [PATCH 355/397] type: React.Key => SafeKey (#877)

---
 .husky/pre-commit        |  1 +
 .prettierignore          |  4 +++
 .prettierrc              |  1 +
 package.json             |  7 ++++
 src/NodeList.tsx         | 20 +++++------
 src/Tree.tsx             | 77 ++++++++++++++++++++--------------------
 src/contextTypes.ts      | 10 +++---
 src/interface.tsx        | 18 ++++++----
 src/util.tsx             | 28 +++++++--------
 src/utils/conductUtil.ts | 38 ++++++++++----------
 src/utils/diffUtil.ts    | 10 +++---
 src/utils/keyUtil.ts     |  6 ++--
 src/utils/treeUtil.ts    | 28 +++++++--------
 13 files changed, 132 insertions(+), 116 deletions(-)
 create mode 100644 .husky/pre-commit

diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 00000000..af5adff9
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+lint-staged
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
index ecb24d33..21437722 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,3 +1,7 @@
+.doc
+.storybook
+es
+lib
 **/*.svg
 **/*.ejs
 **/*.html
diff --git a/.prettierrc b/.prettierrc
index 7f12e041..b0427819 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -4,6 +4,7 @@
   "singleQuote": true,
   "tabWidth": 2,
   "trailingComma": "all",
+  "proseWrap": "never",
   "printWidth": 100,
   "arrowParens": "avoid"
 }
diff --git a/package.json b/package.json
index 7961d2de..5e4a9277 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
     "docs:build": "dumi build",
     "docs:deploy": "gh-pages -d dist",
     "compile": "father build && lessc assets/index.less assets/index.css",
+    "prepare": "husky",
     "prepublishOnly": "npm run compile && np --yolo --no-publish",
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
@@ -41,6 +42,9 @@
     "gh-pages": "npm run docs:build && npm run docs:deploy",
     "now-build": "npm run docs:build"
   },
+  "lint-staged": {
+    "*": "prettier --write --ignore-unknown"
+  },
   "peerDependencies": {
     "react": "*",
     "react-dom": "*"
@@ -62,8 +66,11 @@
     "father": "^4.4.0",
     "gh-pages": "^6.1.1",
     "glob": "^7.1.6",
+    "husky": "^9.1.6",
     "less": "^3.11.1",
+    "lint-staged": "^15.2.10",
     "np": "^10.0.5",
+    "prettier": "^3.3.3",
     "rc-dialog": "^8.1.0",
     "rc-test": "^7.0.15",
     "rc-tooltip": "5.x",
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index f3e18cb9..3ec5f434 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -3,7 +3,7 @@
  */
 
 import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
-import VirtualList, { ListRef } from 'rc-virtual-list';
+import VirtualList, { type ListRef } from 'rc-virtual-list';
 import * as React from 'react';
 import MotionTreeNode from './MotionTreeNode';
 import {
@@ -11,9 +11,9 @@ import {
   DataEntity,
   DataNode,
   FlattenNode,
-  Key,
   KeyEntities,
   ScrollTo,
+  SafeKey,
 } from './interface';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
 import { getKey, getTreeNodeProps } from './utils/treeUtil';
@@ -76,16 +76,16 @@ interface NodeListProps<TreeDataType extends BasicDataNode> {
   selectable?: boolean;
   disabled?: boolean;
 
-  expandedKeys: Key[];
-  selectedKeys: Key[];
-  checkedKeys: Key[];
-  loadedKeys: Key[];
-  loadingKeys: Key[];
-  halfCheckedKeys: Key[];
+  expandedKeys: SafeKey[];
+  selectedKeys: SafeKey[];
+  checkedKeys: SafeKey[];
+  loadedKeys: SafeKey[];
+  loadingKeys: SafeKey[];
+  halfCheckedKeys: SafeKey[];
   keyEntities: KeyEntities;
 
   dragging: boolean;
-  dragOverNodeKey: Key;
+  dragOverNodeKey: SafeKey;
   dropPosition: number;
 
   // Virtual list
@@ -96,7 +96,7 @@ interface NodeListProps<TreeDataType extends BasicDataNode> {
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
-  onActiveChange: (key: Key) => void;
+  onActiveChange: (key: SafeKey) => void;
 
   onListChangeStart: () => void;
   onListChangeEnd: () => void;
diff --git a/src/Tree.tsx b/src/Tree.tsx
index f8d426ef..69e3403f 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -7,15 +7,15 @@ import pickAttrs from 'rc-util/lib/pickAttrs';
 import warning from 'rc-util/lib/warning';
 import * as React from 'react';
 
-import {
+import type {
   NodeDragEventHandler,
   NodeDragEventParams,
   NodeMouseEventHandler,
   NodeMouseEventParams,
-  TreeContext,
 } from './contextTypes';
+import { TreeContext } from './contextTypes';
 import DropIndicator from './DropIndicator';
-import {
+import type {
   BasicDataNode,
   DataNode,
   Direction,
@@ -23,13 +23,13 @@ import {
   FieldNames,
   FlattenNode,
   IconType,
-  Key,
   KeyEntities,
   NodeInstance,
   SafeKey,
   ScrollTo,
 } from './interface';
-import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
+import type { NodeListRef } from './NodeList';
+import NodeList, { MotionEntity, MOTION_KEY } from './NodeList';
 import TreeNode from './TreeNode';
 import {
   arrAdd,
@@ -62,7 +62,7 @@ export interface CheckInfo<TreeDataType extends BasicDataNode = DataNode> {
   nativeEvent: MouseEvent;
   checkedNodes: TreeDataType[];
   checkedNodesPositions?: { node: TreeDataType; pos: string }[];
-  halfCheckedKeys?: Key[];
+  halfCheckedKeys?: SafeKey[];
 }
 
 export interface AllowDropOptions<TreeDataType extends BasicDataNode = DataNode> {
@@ -87,7 +87,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   className?: string;
   style?: React.CSSProperties;
   focusable?: boolean;
-  activeKey?: Key | null;
+  activeKey?: SafeKey | null;
   tabIndex?: number;
   children?: React.ReactNode;
   treeData?: TreeDataType[]; // Generate treeNode by children
@@ -105,12 +105,12 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   defaultExpandParent?: boolean;
   autoExpandParent?: boolean;
   defaultExpandAll?: boolean;
-  defaultExpandedKeys?: Key[];
-  expandedKeys?: Key[];
-  defaultCheckedKeys?: Key[];
-  checkedKeys?: Key[] | { checked: Key[]; halfChecked: Key[] };
-  defaultSelectedKeys?: Key[];
-  selectedKeys?: Key[];
+  defaultExpandedKeys?: SafeKey[];
+  expandedKeys?: SafeKey[];
+  defaultCheckedKeys?: SafeKey[];
+  checkedKeys?: SafeKey[] | { checked: SafeKey[]; halfChecked: SafeKey[] };
+  defaultSelectedKeys?: SafeKey[];
+  selectedKeys?: SafeKey[];
   allowDrop?: AllowDrop<TreeDataType>;
   titleRender?: (node: TreeDataType) => React.ReactNode;
   dropIndicatorRender?: (props: {
@@ -128,7 +128,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   onDoubleClick?: NodeMouseEventHandler<TreeDataType>;
   onScroll?: React.UIEventHandler<HTMLElement>;
   onExpand?: (
-    expandedKeys: Key[],
+    expandedKeys: SafeKey[],
     info: {
       node: EventDataNode<TreeDataType>;
       expanded: boolean;
@@ -136,11 +136,11 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
     },
   ) => void;
   onCheck?: (
-    checked: { checked: Key[]; halfChecked: Key[] } | Key[],
+    checked: { checked: SafeKey[]; halfChecked: SafeKey[] } | SafeKey[],
     info: CheckInfo<TreeDataType>,
   ) => void;
   onSelect?: (
-    selectedKeys: Key[],
+    selectedKeys: SafeKey[],
     info: {
       event: 'select';
       selected: boolean;
@@ -150,26 +150,26 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
     },
   ) => void;
   onLoad?: (
-    loadedKeys: Key[],
+    loadedKeys: SafeKey[],
     info: {
       event: 'load';
       node: EventDataNode<TreeDataType>;
     },
   ) => void;
   loadData?: (treeNode: EventDataNode<TreeDataType>) => Promise<any>;
-  loadedKeys?: Key[];
+  loadedKeys?: SafeKey[];
   onMouseEnter?: (info: NodeMouseEventParams<TreeDataType>) => void;
   onMouseLeave?: (info: NodeMouseEventParams<TreeDataType>) => void;
   onRightClick?: (info: { event: React.MouseEvent; node: EventDataNode<TreeDataType> }) => void;
   onDragStart?: (info: NodeDragEventParams<TreeDataType>) => void;
-  onDragEnter?: (info: NodeDragEventParams<TreeDataType> & { expandedKeys: Key[] }) => void;
+  onDragEnter?: (info: NodeDragEventParams<TreeDataType> & { expandedKeys: SafeKey[] }) => void;
   onDragOver?: (info: NodeDragEventParams<TreeDataType>) => void;
   onDragLeave?: (info: NodeDragEventParams<TreeDataType>) => void;
   onDragEnd?: (info: NodeDragEventParams<TreeDataType>) => void;
   onDrop?: (
     info: NodeDragEventParams<TreeDataType> & {
       dragNode: EventDataNode<TreeDataType>;
-      dragNodesKeys: Key[];
+      dragNodesKeys: SafeKey[];
       dropPosition: number;
       dropToGap: boolean;
     },
@@ -178,7 +178,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
    * Used for `rc-tree-select` only.
    * Do not use in your production code directly since this will be refactor.
    */
-  onActiveChange?: (key: Key) => void;
+  onActiveChange?: (key: SafeKey) => void;
   filterTreeNode?: (treeNode: EventDataNode<TreeDataType>) => boolean;
   motion?: any;
   switcherIcon?: IconType;
@@ -201,30 +201,30 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
 
   indent: number | null;
 
-  selectedKeys: Key[];
-  checkedKeys: Key[];
-  halfCheckedKeys: Key[];
-  loadedKeys: Key[];
-  loadingKeys: Key[];
-  expandedKeys: Key[];
+  selectedKeys: SafeKey[];
+  checkedKeys: SafeKey[];
+  halfCheckedKeys: SafeKey[];
+  loadedKeys: SafeKey[];
+  loadingKeys: SafeKey[];
+  expandedKeys: SafeKey[];
 
-  draggingNodeKey: Key;
-  dragChildrenKeys: Key[];
+  draggingNodeKey: SafeKey;
+  dragChildrenKeys: SafeKey[];
 
   // for details see comment in Tree.state
   dropPosition: -1 | 0 | 1 | null;
   dropLevelOffset: number | null;
-  dropContainerKey: Key | null;
-  dropTargetKey: Key | null;
+  dropContainerKey: SafeKey | null;
+  dropTargetKey: SafeKey | null;
   dropTargetPos: string | null;
   dropAllowed: boolean;
-  dragOverNodeKey: Key | null;
+  dragOverNodeKey: SafeKey | null;
 
   treeData: TreeDataType[];
   flattenNodes: FlattenNode<TreeDataType>[];
 
   focused: boolean;
-  activeKey: Key | null;
+  activeKey: SafeKey | null;
 
   // Record if list is changing
   listChanging: boolean;
@@ -401,7 +401,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       delete cloneKeyEntities[MOTION_KEY];
 
       // Only take the key who has the children to enhance the performance
-      const nextExpandedKeys: React.Key[] = [];
+      const nextExpandedKeys: SafeKey[] = [];
       Object.keys(cloneKeyEntities).forEach(key => {
         const entity = cloneKeyEntities[key];
         if (entity.children && entity.children.length) {
@@ -1014,9 +1014,8 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
             }));
 
             // If exceed max retry times, we give up retry
-            this.loadingRetryTimes[key as SafeKey] =
-              (this.loadingRetryTimes[key as SafeKey] || 0) + 1;
-            if (this.loadingRetryTimes[key as SafeKey] >= MAX_RETRY_TIMES) {
+            this.loadingRetryTimes[key] = (this.loadingRetryTimes[key] || 0) + 1;
+            if (this.loadingRetryTimes[key] >= MAX_RETRY_TIMES) {
               const { loadedKeys: currentLoadedKeys } = this.state;
 
               warning(false, 'Retry for `loadData` many times but still failed. No more retry.');
@@ -1104,7 +1103,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
   // =========================== Expanded ===========================
   /** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
-  setExpandedKeys = (expandedKeys: Key[]) => {
+  setExpandedKeys = (expandedKeys: SafeKey[]) => {
     const { treeData, fieldNames } = this.state;
 
     const flattenNodes: FlattenNode<TreeDataType>[] = flattenTreeData<TreeDataType>(
@@ -1194,7 +1193,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
   };
 
   // =========================== Keyboard ===========================
-  onActiveChange = (newActiveKey: Key | null) => {
+  onActiveChange = (newActiveKey: SafeKey | null) => {
     const { activeKey } = this.state;
     const { onActiveChange, itemScrollOffset = 0 } = this.props;
 
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 2132ff26..706176b7 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -9,7 +9,7 @@ import {
   Direction,
   EventDataNode,
   IconType,
-  Key,
+  SafeKey,
   KeyEntities,
   NodeInstance,
 } from './interface';
@@ -46,15 +46,15 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
   icon: IconType;
   switcherIcon: IconType;
   draggable?: DraggableConfig;
-  draggingNodeKey?: Key;
+  draggingNodeKey?: SafeKey;
   checkable: boolean | React.ReactNode;
   checkStrictly: boolean;
   disabled: boolean;
   keyEntities: KeyEntities;
   // for details see comment in Tree.state (Tree.tsx)
   dropLevelOffset?: number;
-  dropContainerKey: Key | null;
-  dropTargetKey: Key | null;
+  dropContainerKey: SafeKey | null;
+  dropTargetKey: SafeKey | null;
   dropPosition: -1 | 0 | 1 | null;
   indent: number | null;
   dropIndicatorRender: (props: {
@@ -64,7 +64,7 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
     prefixCls;
     direction: Direction;
   }) => React.ReactNode;
-  dragOverNodeKey: Key | null;
+  dragOverNodeKey: SafeKey | null;
   direction: Direction;
 
   loadData: (treeNode: EventDataNode<TreeDataType>) => Promise<void>;
diff --git a/src/interface.tsx b/src/interface.tsx
index 6c5e5ef4..cbc7a433 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
 
 export type { ScrollTo } from 'rc-virtual-list/lib/List';
 export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
-  eventKey?: Key; // Pass by parent `cloneElement`
+  eventKey?: SafeKey; // Pass by parent `cloneElement`
   prefixCls?: string;
   className?: string;
   style?: React.CSSProperties;
@@ -27,6 +27,7 @@ export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
   isEnd?: boolean[];
   active?: boolean;
   onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
+  indentWidth?: number;
 
   // By user
   isLeaf?: boolean;
@@ -69,15 +70,18 @@ export type Key = React.Key;
  */
 export type SafeKey = Exclude<Key, bigint>;
 
-export type KeyEntities<DateType extends BasicDataNode = any> = Record<SafeKey, DataEntity<DateType>>;
+export type KeyEntities<DateType extends BasicDataNode = any> = Record<
+  SafeKey,
+  DataEntity<DateType>
+>;
 
 export type DataNode = FieldDataNode<{
-  key: Key;
+  key: SafeKey;
   title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
 }>;
 
 export type EventDataNode<TreeDataType> = {
-  key: Key;
+  key: SafeKey;
   expanded: boolean;
   selected: boolean;
   checked: boolean;
@@ -110,7 +114,7 @@ export type NodeInstance<TreeDataType extends BasicDataNode = DataNode> = React.
 export interface Entity {
   node: NodeElement;
   index: number;
-  key: Key;
+  key: SafeKey;
   pos: string;
   parent?: Entity;
   children?: Entity[];
@@ -131,12 +135,12 @@ export interface FlattenNode<TreeDataType extends BasicDataNode = DataNode> {
   pos: string;
   data: TreeDataType;
   title: React.ReactNode;
-  key: Key;
+  key: SafeKey;
   isStart: boolean[];
   isEnd: boolean[];
 }
 
-export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
+export type GetKey<RecordType> = (record: RecordType, index?: number) => SafeKey;
 
 export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;
 
diff --git a/src/util.tsx b/src/util.tsx
index 5600c3d1..9b7b3662 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -11,10 +11,10 @@ import {
   DataNode,
   Direction,
   FlattenNode,
-  Key,
   KeyEntities,
   NodeElement,
   NodeInstance,
+  SafeKey,
 } from './interface';
 import { AllowDrop, TreeProps } from './Tree';
 import TreeNode from './TreeNode';
@@ -22,7 +22,7 @@ import getEntity from './utils/keyUtil';
 
 export { getPosition, isTreeNode } from './utils/treeUtil';
 
-export function arrDel(list: Key[], value: Key) {
+export function arrDel(list: SafeKey[], value: SafeKey) {
   if (!list) return [];
   const clone = list.slice();
   const index = clone.indexOf(value);
@@ -32,7 +32,7 @@ export function arrDel(list: Key[], value: Key) {
   return clone;
 }
 
-export function arrAdd(list: Key[], value: Key) {
+export function arrAdd(list: SafeKey[], value: SafeKey) {
   const clone = (list || []).slice();
   if (clone.indexOf(value) === -1) {
     clone.push(value);
@@ -45,9 +45,9 @@ export function posToArr(pos: string) {
 }
 
 export function getDragChildrenKeys<TreeDataType extends BasicDataNode = DataNode>(
-  dragNodeKey: Key,
+  dragNodeKey: SafeKey,
   keyEntities: KeyEntities<TreeDataType>,
-): Key[] {
+): SafeKey[] {
   // not contains self
   // self for left or right drag
   const dragChildrenKeys = [];
@@ -95,15 +95,15 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
   allowDrop: AllowDrop<TreeDataType>,
   flattenedNodes: FlattenNode<TreeDataType>[],
   keyEntities: KeyEntities<TreeDataType>,
-  expandKeys: Key[],
+  expandKeys: SafeKey[],
   direction: Direction,
 ): {
   dropPosition: -1 | 0 | 1;
   dropLevelOffset: number;
-  dropTargetKey: Key;
+  dropTargetKey: SafeKey;
   dropTargetPos: string;
-  dropContainerKey: Key;
-  dragOverNodeKey: Key;
+  dropContainerKey: SafeKey;
+  dragOverNodeKey: SafeKey;
   dropAllowed: boolean;
 } {
   const { clientX, clientY } = event;
@@ -266,7 +266,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
  * @param props
  * @returns [string]
  */
-export function calcSelectedKeys(selectedKeys: Key[], props: TreeProps) {
+export function calcSelectedKeys(selectedKeys: SafeKey[], props: TreeProps) {
   if (!selectedKeys) return undefined;
 
   const { multiple } = props;
@@ -303,7 +303,7 @@ export function convertDataToTree(
 /**
  * Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style
  */
-export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Key[] }) {
+export function parseCheckedKeys(keys: SafeKey[] | { checked: SafeKey[]; halfChecked: SafeKey[] }) {
   if (!keys) {
     return null;
   }
@@ -334,10 +334,10 @@ export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Ke
  * @param keyList
  * @param keyEntities
  */
-export function conductExpandParent(keyList: Key[], keyEntities: KeyEntities): Key[] {
-  const expandedKeys = new Set<Key>();
+export function conductExpandParent(keyList: SafeKey[], keyEntities: KeyEntities): SafeKey[] {
+  const expandedKeys = new Set<SafeKey>();
 
-  function conductUp(key: Key) {
+  function conductUp(key: SafeKey) {
     if (expandedKeys.has(key)) return;
 
     const entity = getEntity(keyEntities, key);
diff --git a/src/utils/conductUtil.ts b/src/utils/conductUtil.ts
index 9f1fda9c..84f2030e 100644
--- a/src/utils/conductUtil.ts
+++ b/src/utils/conductUtil.ts
@@ -4,18 +4,18 @@ import {
   DataEntity,
   DataNode,
   GetCheckDisabled,
-  Key,
+  SafeKey,
   KeyEntities,
 } from '../interface';
 import getEntity from './keyUtil';
 
 interface ConductReturnType {
-  checkedKeys: Key[];
-  halfCheckedKeys: Key[];
+  checkedKeys: SafeKey[];
+  halfCheckedKeys: SafeKey[];
 }
 
-function removeFromCheckedKeys(halfCheckedKeys: Set<Key>, checkedKeys: Set<Key>) {
-  const filteredKeys = new Set<Key>();
+function removeFromCheckedKeys(halfCheckedKeys: Set<SafeKey>, checkedKeys: Set<SafeKey>) {
+  const filteredKeys = new Set<SafeKey>();
   halfCheckedKeys.forEach(key => {
     if (!checkedKeys.has(key)) {
       filteredKeys.add(key);
@@ -31,13 +31,13 @@ export function isCheckDisabled<TreeDataType>(node: TreeDataType) {
 
 // Fill miss keys
 function fillConductCheck<TreeDataType extends BasicDataNode = DataNode>(
-  keys: Set<Key>,
+  keys: Set<SafeKey>,
   levelEntities: Map<number, Set<DataEntity<TreeDataType>>>,
   maxLevel: number,
   syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
-  const checkedKeys = new Set<Key>(keys);
-  const halfCheckedKeys = new Set<Key>();
+  const checkedKeys = new Set<SafeKey>(keys);
+  const halfCheckedKeys = new Set<SafeKey>();
 
   // Add checked keys top to bottom
   for (let level = 0; level <= maxLevel; level += 1) {
@@ -56,7 +56,7 @@ function fillConductCheck<TreeDataType extends BasicDataNode = DataNode>(
   }
 
   // Add checked keys from bottom to top
-  const visitedKeys = new Set<Key>();
+  const visitedKeys = new Set<SafeKey>();
   for (let level = maxLevel; level >= 0; level -= 1) {
     const entities = levelEntities.get(level) || new Set();
     entities.forEach(entity => {
@@ -107,14 +107,14 @@ function fillConductCheck<TreeDataType extends BasicDataNode = DataNode>(
 
 // Remove useless key
 function cleanConductCheck<TreeDataType extends BasicDataNode = DataNode>(
-  keys: Set<Key>,
-  halfKeys: Key[],
+  keys: Set<SafeKey>,
+  halfKeys: SafeKey[],
   levelEntities: Map<number, Set<DataEntity<TreeDataType>>>,
   maxLevel: number,
   syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
-  const checkedKeys = new Set<Key>(keys);
-  let halfCheckedKeys = new Set<Key>(halfKeys);
+  const checkedKeys = new Set<SafeKey>(keys);
+  let halfCheckedKeys = new Set<SafeKey>(halfKeys);
 
   // Remove checked keys from top to bottom
   for (let level = 0; level <= maxLevel; level += 1) {
@@ -133,8 +133,8 @@ function cleanConductCheck<TreeDataType extends BasicDataNode = DataNode>(
   }
 
   // Remove checked keys form bottom to top
-  halfCheckedKeys = new Set<Key>();
-  const visitedKeys = new Set<Key>();
+  halfCheckedKeys = new Set<SafeKey>();
+  const visitedKeys = new Set<SafeKey>();
   for (let level = maxLevel; level >= 0; level -= 1) {
     const entities = levelEntities.get(level) || new Set();
 
@@ -191,12 +191,12 @@ function cleanConductCheck<TreeDataType extends BasicDataNode = DataNode>(
  * @param mode `fill` to fill missing key, `clean` to remove useless key
  */
 export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
-  keyList: Key[],
-  checked: true | { checked: false; halfCheckedKeys: Key[] },
+  keyList: SafeKey[],
+  checked: true | { checked: false; halfCheckedKeys: SafeKey[] },
   keyEntities: KeyEntities<TreeDataType>,
   getCheckDisabled?: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
-  const warningMissKeys: Key[] = [];
+  const warningMissKeys: SafeKey[] = [];
 
   let syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>;
   if (getCheckDisabled) {
@@ -206,7 +206,7 @@ export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
   }
 
   // We only handle exist keys
-  const keys = new Set<Key>(
+  const keys = new Set<SafeKey>(
     keyList.filter(key => {
       const hasEntity = !!getEntity(keyEntities, key);
       if (!hasEntity) {
diff --git a/src/utils/diffUtil.ts b/src/utils/diffUtil.ts
index 85496435..5114fc8c 100644
--- a/src/utils/diffUtil.ts
+++ b/src/utils/diffUtil.ts
@@ -1,6 +1,6 @@
-import { Key, FlattenNode } from '../interface';
+import { SafeKey, FlattenNode } from '../interface';
 
-export function findExpandedKeys(prev: Key[] = [], next: Key[] = []) {
+export function findExpandedKeys(prev: SafeKey[] = [], next: SafeKey[] = []) {
   const prevLen = prev.length;
   const nextLen = next.length;
 
@@ -8,8 +8,8 @@ export function findExpandedKeys(prev: Key[] = [], next: Key[] = []) {
     return { add: false, key: null };
   }
 
-  function find(shorter: Key[], longer: Key[]) {
-    const cache: Map<Key, boolean> = new Map();
+  function find(shorter: SafeKey[], longer: SafeKey[]) {
+    const cache: Map<SafeKey, boolean> = new Map();
     shorter.forEach(key => {
       cache.set(key, true);
     });
@@ -32,7 +32,7 @@ export function findExpandedKeys(prev: Key[] = [], next: Key[] = []) {
   };
 }
 
-export function getExpandRange(shorter: FlattenNode[], longer: FlattenNode[], key: Key) {
+export function getExpandRange(shorter: FlattenNode[], longer: FlattenNode[], key: SafeKey) {
   const shorterStartIndex = shorter.findIndex(data => data.key === key);
   const shorterEndNode = shorter[shorterStartIndex + 1];
   const longerStartIndex = longer.findIndex(data => data.key === key);
diff --git a/src/utils/keyUtil.ts b/src/utils/keyUtil.ts
index ae464e16..6722c409 100644
--- a/src/utils/keyUtil.ts
+++ b/src/utils/keyUtil.ts
@@ -1,5 +1,5 @@
-import type { Key, KeyEntities, SafeKey } from '../interface';
+import type { KeyEntities, SafeKey } from '../interface';
 
-export default function getEntity<T = any>(keyEntities: KeyEntities<T>, key: Key) {
-  return keyEntities[key as SafeKey];
+export default function getEntity<T = any>(keyEntities: KeyEntities<T>, key: SafeKey) {
+  return keyEntities[key];
 }
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 701c1b87..d0f9babc 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -26,7 +26,7 @@ export function isTreeNode(node: NodeElement) {
   return node && node.type && node.type.isTreeNode;
 }
 
-export function getKey(key: Key, pos: string) {
+export function getKey(key: SafeKey, pos: string) {
   if (key !== null && key !== undefined) {
     return key;
   }
@@ -92,7 +92,7 @@ export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
         const { children, ...rest } = treeNode.props;
 
         const dataNode: DataNode = {
-          key: key as Key,
+          key,
           ...rest,
         };
 
@@ -117,7 +117,7 @@ export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
  */
 export function flattenTreeData<TreeDataType extends BasicDataNode = DataNode>(
   treeNodeList: TreeDataType[],
-  expandedKeys: Key[] | true,
+  expandedKeys: SafeKey[] | true,
   fieldNames: FieldNames,
 ): FlattenNode<TreeDataType>[] {
   const {
@@ -198,7 +198,7 @@ export function traverseDataNodes(
     node: DataNode;
     index: number;
     pos: string;
-    key: Key;
+    key: SafeKey;
     parentPos: string | number;
     level: number;
     nodes: DataNode[];
@@ -222,7 +222,7 @@ export function traverseDataNodes(
   const mergeChildrenPropName = childrenPropName || fieldChildren;
 
   // Get keys
-  let syntheticGetKey: (node: DataNode, pos?: string) => Key;
+  let syntheticGetKey: (node: DataNode, pos?: string) => SafeKey;
   if (externalGetKey) {
     if (typeof externalGetKey === 'string') {
       syntheticGetKey = (node: DataNode) => (node as any)[externalGetKey as string];
@@ -331,7 +331,7 @@ export function convertDataToEntities(
       const mergedKey = getKey(key, pos);
 
       posEntities[pos] = entity;
-      keyEntities[mergedKey as SafeKey] = entity;
+      keyEntities[mergedKey] = entity;
 
       // Fill children
       entity.parent = posEntities[parentPos];
@@ -355,13 +355,13 @@ export function convertDataToEntities(
 }
 
 export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = DataNode> {
-  expandedKeys: Key[];
-  selectedKeys: Key[];
-  loadedKeys: Key[];
-  loadingKeys: Key[];
-  checkedKeys: Key[];
-  halfCheckedKeys: Key[];
-  dragOverNodeKey: Key;
+  expandedKeys: SafeKey[];
+  selectedKeys: SafeKey[];
+  loadedKeys: SafeKey[];
+  loadingKeys: SafeKey[];
+  checkedKeys: SafeKey[];
+  halfCheckedKeys: SafeKey[];
+  dragOverNodeKey: SafeKey;
   dropPosition: number;
   keyEntities: KeyEntities<TreeDataType>;
 }
@@ -370,7 +370,7 @@ export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = Data
  * Get TreeNode props with Tree props.
  */
 export function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(
-  key: Key,
+  key: SafeKey,
   {
     expandedKeys,
     selectedKeys,

From d4fa7bffb18a1972d820a9057e8fe1631f6b5539 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Mon, 14 Oct 2024 15:41:04 +0800
Subject: [PATCH 356/397] 5.10.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 5e4a9277..0b924637 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.9.0",
+  "version": "5.10.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 470db441608c113d28335c323da4f4ac2418a114 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 14 Oct 2024 17:59:08 +0800
Subject: [PATCH 357/397] chore(deps-dev): bump rc-tooltip from 5.3.1 to 6.2.1
 (#870)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0b924637..2f86b241 100644
--- a/package.json
+++ b/package.json
@@ -73,7 +73,7 @@
     "prettier": "^3.3.3",
     "rc-dialog": "^8.1.0",
     "rc-test": "^7.0.15",
-    "rc-tooltip": "5.x",
+    "rc-tooltip": "6.x",
     "rc-trigger": "^5.0.7",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",

From 82a1dafdac483afd3f302395115d3a6323a891d1 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 15 Oct 2024 18:40:39 +0800
Subject: [PATCH 358/397] Revert "type: React.Key => SafeKey" (#882)

---
 .husky/pre-commit        |  1 -
 .prettierignore          |  4 ---
 .prettierrc              |  1 -
 package.json             |  7 ----
 src/NodeList.tsx         | 20 +++++------
 src/Tree.tsx             | 77 ++++++++++++++++++++--------------------
 src/contextTypes.ts      | 10 +++---
 src/interface.tsx        | 18 ++++------
 src/util.tsx             | 28 +++++++--------
 src/utils/conductUtil.ts | 38 ++++++++++----------
 src/utils/diffUtil.ts    | 10 +++---
 src/utils/keyUtil.ts     |  6 ++--
 src/utils/treeUtil.ts    | 28 +++++++--------
 13 files changed, 116 insertions(+), 132 deletions(-)
 delete mode 100644 .husky/pre-commit

diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100644
index af5adff9..00000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1 +0,0 @@
-lint-staged
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
index 21437722..ecb24d33 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,7 +1,3 @@
-.doc
-.storybook
-es
-lib
 **/*.svg
 **/*.ejs
 **/*.html
diff --git a/.prettierrc b/.prettierrc
index b0427819..7f12e041 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -4,7 +4,6 @@
   "singleQuote": true,
   "tabWidth": 2,
   "trailingComma": "all",
-  "proseWrap": "never",
   "printWidth": 100,
   "arrowParens": "avoid"
 }
diff --git a/package.json b/package.json
index 2f86b241..30619f58 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,6 @@
     "docs:build": "dumi build",
     "docs:deploy": "gh-pages -d dist",
     "compile": "father build && lessc assets/index.less assets/index.css",
-    "prepare": "husky",
     "prepublishOnly": "npm run compile && np --yolo --no-publish",
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
@@ -42,9 +41,6 @@
     "gh-pages": "npm run docs:build && npm run docs:deploy",
     "now-build": "npm run docs:build"
   },
-  "lint-staged": {
-    "*": "prettier --write --ignore-unknown"
-  },
   "peerDependencies": {
     "react": "*",
     "react-dom": "*"
@@ -66,11 +62,8 @@
     "father": "^4.4.0",
     "gh-pages": "^6.1.1",
     "glob": "^7.1.6",
-    "husky": "^9.1.6",
     "less": "^3.11.1",
-    "lint-staged": "^15.2.10",
     "np": "^10.0.5",
-    "prettier": "^3.3.3",
     "rc-dialog": "^8.1.0",
     "rc-test": "^7.0.15",
     "rc-tooltip": "6.x",
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 3ec5f434..f3e18cb9 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -3,7 +3,7 @@
  */
 
 import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
-import VirtualList, { type ListRef } from 'rc-virtual-list';
+import VirtualList, { ListRef } from 'rc-virtual-list';
 import * as React from 'react';
 import MotionTreeNode from './MotionTreeNode';
 import {
@@ -11,9 +11,9 @@ import {
   DataEntity,
   DataNode,
   FlattenNode,
+  Key,
   KeyEntities,
   ScrollTo,
-  SafeKey,
 } from './interface';
 import { findExpandedKeys, getExpandRange } from './utils/diffUtil';
 import { getKey, getTreeNodeProps } from './utils/treeUtil';
@@ -76,16 +76,16 @@ interface NodeListProps<TreeDataType extends BasicDataNode> {
   selectable?: boolean;
   disabled?: boolean;
 
-  expandedKeys: SafeKey[];
-  selectedKeys: SafeKey[];
-  checkedKeys: SafeKey[];
-  loadedKeys: SafeKey[];
-  loadingKeys: SafeKey[];
-  halfCheckedKeys: SafeKey[];
+  expandedKeys: Key[];
+  selectedKeys: Key[];
+  checkedKeys: Key[];
+  loadedKeys: Key[];
+  loadingKeys: Key[];
+  halfCheckedKeys: Key[];
   keyEntities: KeyEntities;
 
   dragging: boolean;
-  dragOverNodeKey: SafeKey;
+  dragOverNodeKey: Key;
   dropPosition: number;
 
   // Virtual list
@@ -96,7 +96,7 @@ interface NodeListProps<TreeDataType extends BasicDataNode> {
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
-  onActiveChange: (key: SafeKey) => void;
+  onActiveChange: (key: Key) => void;
 
   onListChangeStart: () => void;
   onListChangeEnd: () => void;
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 69e3403f..f8d426ef 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -7,15 +7,15 @@ import pickAttrs from 'rc-util/lib/pickAttrs';
 import warning from 'rc-util/lib/warning';
 import * as React from 'react';
 
-import type {
+import {
   NodeDragEventHandler,
   NodeDragEventParams,
   NodeMouseEventHandler,
   NodeMouseEventParams,
+  TreeContext,
 } from './contextTypes';
-import { TreeContext } from './contextTypes';
 import DropIndicator from './DropIndicator';
-import type {
+import {
   BasicDataNode,
   DataNode,
   Direction,
@@ -23,13 +23,13 @@ import type {
   FieldNames,
   FlattenNode,
   IconType,
+  Key,
   KeyEntities,
   NodeInstance,
   SafeKey,
   ScrollTo,
 } from './interface';
-import type { NodeListRef } from './NodeList';
-import NodeList, { MotionEntity, MOTION_KEY } from './NodeList';
+import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
 import TreeNode from './TreeNode';
 import {
   arrAdd,
@@ -62,7 +62,7 @@ export interface CheckInfo<TreeDataType extends BasicDataNode = DataNode> {
   nativeEvent: MouseEvent;
   checkedNodes: TreeDataType[];
   checkedNodesPositions?: { node: TreeDataType; pos: string }[];
-  halfCheckedKeys?: SafeKey[];
+  halfCheckedKeys?: Key[];
 }
 
 export interface AllowDropOptions<TreeDataType extends BasicDataNode = DataNode> {
@@ -87,7 +87,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   className?: string;
   style?: React.CSSProperties;
   focusable?: boolean;
-  activeKey?: SafeKey | null;
+  activeKey?: Key | null;
   tabIndex?: number;
   children?: React.ReactNode;
   treeData?: TreeDataType[]; // Generate treeNode by children
@@ -105,12 +105,12 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   defaultExpandParent?: boolean;
   autoExpandParent?: boolean;
   defaultExpandAll?: boolean;
-  defaultExpandedKeys?: SafeKey[];
-  expandedKeys?: SafeKey[];
-  defaultCheckedKeys?: SafeKey[];
-  checkedKeys?: SafeKey[] | { checked: SafeKey[]; halfChecked: SafeKey[] };
-  defaultSelectedKeys?: SafeKey[];
-  selectedKeys?: SafeKey[];
+  defaultExpandedKeys?: Key[];
+  expandedKeys?: Key[];
+  defaultCheckedKeys?: Key[];
+  checkedKeys?: Key[] | { checked: Key[]; halfChecked: Key[] };
+  defaultSelectedKeys?: Key[];
+  selectedKeys?: Key[];
   allowDrop?: AllowDrop<TreeDataType>;
   titleRender?: (node: TreeDataType) => React.ReactNode;
   dropIndicatorRender?: (props: {
@@ -128,7 +128,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   onDoubleClick?: NodeMouseEventHandler<TreeDataType>;
   onScroll?: React.UIEventHandler<HTMLElement>;
   onExpand?: (
-    expandedKeys: SafeKey[],
+    expandedKeys: Key[],
     info: {
       node: EventDataNode<TreeDataType>;
       expanded: boolean;
@@ -136,11 +136,11 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
     },
   ) => void;
   onCheck?: (
-    checked: { checked: SafeKey[]; halfChecked: SafeKey[] } | SafeKey[],
+    checked: { checked: Key[]; halfChecked: Key[] } | Key[],
     info: CheckInfo<TreeDataType>,
   ) => void;
   onSelect?: (
-    selectedKeys: SafeKey[],
+    selectedKeys: Key[],
     info: {
       event: 'select';
       selected: boolean;
@@ -150,26 +150,26 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
     },
   ) => void;
   onLoad?: (
-    loadedKeys: SafeKey[],
+    loadedKeys: Key[],
     info: {
       event: 'load';
       node: EventDataNode<TreeDataType>;
     },
   ) => void;
   loadData?: (treeNode: EventDataNode<TreeDataType>) => Promise<any>;
-  loadedKeys?: SafeKey[];
+  loadedKeys?: Key[];
   onMouseEnter?: (info: NodeMouseEventParams<TreeDataType>) => void;
   onMouseLeave?: (info: NodeMouseEventParams<TreeDataType>) => void;
   onRightClick?: (info: { event: React.MouseEvent; node: EventDataNode<TreeDataType> }) => void;
   onDragStart?: (info: NodeDragEventParams<TreeDataType>) => void;
-  onDragEnter?: (info: NodeDragEventParams<TreeDataType> & { expandedKeys: SafeKey[] }) => void;
+  onDragEnter?: (info: NodeDragEventParams<TreeDataType> & { expandedKeys: Key[] }) => void;
   onDragOver?: (info: NodeDragEventParams<TreeDataType>) => void;
   onDragLeave?: (info: NodeDragEventParams<TreeDataType>) => void;
   onDragEnd?: (info: NodeDragEventParams<TreeDataType>) => void;
   onDrop?: (
     info: NodeDragEventParams<TreeDataType> & {
       dragNode: EventDataNode<TreeDataType>;
-      dragNodesKeys: SafeKey[];
+      dragNodesKeys: Key[];
       dropPosition: number;
       dropToGap: boolean;
     },
@@ -178,7 +178,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
    * Used for `rc-tree-select` only.
    * Do not use in your production code directly since this will be refactor.
    */
-  onActiveChange?: (key: SafeKey) => void;
+  onActiveChange?: (key: Key) => void;
   filterTreeNode?: (treeNode: EventDataNode<TreeDataType>) => boolean;
   motion?: any;
   switcherIcon?: IconType;
@@ -201,30 +201,30 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
 
   indent: number | null;
 
-  selectedKeys: SafeKey[];
-  checkedKeys: SafeKey[];
-  halfCheckedKeys: SafeKey[];
-  loadedKeys: SafeKey[];
-  loadingKeys: SafeKey[];
-  expandedKeys: SafeKey[];
+  selectedKeys: Key[];
+  checkedKeys: Key[];
+  halfCheckedKeys: Key[];
+  loadedKeys: Key[];
+  loadingKeys: Key[];
+  expandedKeys: Key[];
 
-  draggingNodeKey: SafeKey;
-  dragChildrenKeys: SafeKey[];
+  draggingNodeKey: Key;
+  dragChildrenKeys: Key[];
 
   // for details see comment in Tree.state
   dropPosition: -1 | 0 | 1 | null;
   dropLevelOffset: number | null;
-  dropContainerKey: SafeKey | null;
-  dropTargetKey: SafeKey | null;
+  dropContainerKey: Key | null;
+  dropTargetKey: Key | null;
   dropTargetPos: string | null;
   dropAllowed: boolean;
-  dragOverNodeKey: SafeKey | null;
+  dragOverNodeKey: Key | null;
 
   treeData: TreeDataType[];
   flattenNodes: FlattenNode<TreeDataType>[];
 
   focused: boolean;
-  activeKey: SafeKey | null;
+  activeKey: Key | null;
 
   // Record if list is changing
   listChanging: boolean;
@@ -401,7 +401,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       delete cloneKeyEntities[MOTION_KEY];
 
       // Only take the key who has the children to enhance the performance
-      const nextExpandedKeys: SafeKey[] = [];
+      const nextExpandedKeys: React.Key[] = [];
       Object.keys(cloneKeyEntities).forEach(key => {
         const entity = cloneKeyEntities[key];
         if (entity.children && entity.children.length) {
@@ -1014,8 +1014,9 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
             }));
 
             // If exceed max retry times, we give up retry
-            this.loadingRetryTimes[key] = (this.loadingRetryTimes[key] || 0) + 1;
-            if (this.loadingRetryTimes[key] >= MAX_RETRY_TIMES) {
+            this.loadingRetryTimes[key as SafeKey] =
+              (this.loadingRetryTimes[key as SafeKey] || 0) + 1;
+            if (this.loadingRetryTimes[key as SafeKey] >= MAX_RETRY_TIMES) {
               const { loadedKeys: currentLoadedKeys } = this.state;
 
               warning(false, 'Retry for `loadData` many times but still failed. No more retry.');
@@ -1103,7 +1104,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
   // =========================== Expanded ===========================
   /** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
-  setExpandedKeys = (expandedKeys: SafeKey[]) => {
+  setExpandedKeys = (expandedKeys: Key[]) => {
     const { treeData, fieldNames } = this.state;
 
     const flattenNodes: FlattenNode<TreeDataType>[] = flattenTreeData<TreeDataType>(
@@ -1193,7 +1194,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
   };
 
   // =========================== Keyboard ===========================
-  onActiveChange = (newActiveKey: SafeKey | null) => {
+  onActiveChange = (newActiveKey: Key | null) => {
     const { activeKey } = this.state;
     const { onActiveChange, itemScrollOffset = 0 } = this.props;
 
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 706176b7..2132ff26 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -9,7 +9,7 @@ import {
   Direction,
   EventDataNode,
   IconType,
-  SafeKey,
+  Key,
   KeyEntities,
   NodeInstance,
 } from './interface';
@@ -46,15 +46,15 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
   icon: IconType;
   switcherIcon: IconType;
   draggable?: DraggableConfig;
-  draggingNodeKey?: SafeKey;
+  draggingNodeKey?: Key;
   checkable: boolean | React.ReactNode;
   checkStrictly: boolean;
   disabled: boolean;
   keyEntities: KeyEntities;
   // for details see comment in Tree.state (Tree.tsx)
   dropLevelOffset?: number;
-  dropContainerKey: SafeKey | null;
-  dropTargetKey: SafeKey | null;
+  dropContainerKey: Key | null;
+  dropTargetKey: Key | null;
   dropPosition: -1 | 0 | 1 | null;
   indent: number | null;
   dropIndicatorRender: (props: {
@@ -64,7 +64,7 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
     prefixCls;
     direction: Direction;
   }) => React.ReactNode;
-  dragOverNodeKey: SafeKey | null;
+  dragOverNodeKey: Key | null;
   direction: Direction;
 
   loadData: (treeNode: EventDataNode<TreeDataType>) => Promise<void>;
diff --git a/src/interface.tsx b/src/interface.tsx
index cbc7a433..6c5e5ef4 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
 
 export type { ScrollTo } from 'rc-virtual-list/lib/List';
 export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
-  eventKey?: SafeKey; // Pass by parent `cloneElement`
+  eventKey?: Key; // Pass by parent `cloneElement`
   prefixCls?: string;
   className?: string;
   style?: React.CSSProperties;
@@ -27,7 +27,6 @@ export interface TreeNodeProps<TreeDataType extends BasicDataNode = DataNode> {
   isEnd?: boolean[];
   active?: boolean;
   onMouseMove?: React.MouseEventHandler<HTMLDivElement>;
-  indentWidth?: number;
 
   // By user
   isLeaf?: boolean;
@@ -70,18 +69,15 @@ export type Key = React.Key;
  */
 export type SafeKey = Exclude<Key, bigint>;
 
-export type KeyEntities<DateType extends BasicDataNode = any> = Record<
-  SafeKey,
-  DataEntity<DateType>
->;
+export type KeyEntities<DateType extends BasicDataNode = any> = Record<SafeKey, DataEntity<DateType>>;
 
 export type DataNode = FieldDataNode<{
-  key: SafeKey;
+  key: Key;
   title?: React.ReactNode | ((data: DataNode) => React.ReactNode);
 }>;
 
 export type EventDataNode<TreeDataType> = {
-  key: SafeKey;
+  key: Key;
   expanded: boolean;
   selected: boolean;
   checked: boolean;
@@ -114,7 +110,7 @@ export type NodeInstance<TreeDataType extends BasicDataNode = DataNode> = React.
 export interface Entity {
   node: NodeElement;
   index: number;
-  key: SafeKey;
+  key: Key;
   pos: string;
   parent?: Entity;
   children?: Entity[];
@@ -135,12 +131,12 @@ export interface FlattenNode<TreeDataType extends BasicDataNode = DataNode> {
   pos: string;
   data: TreeDataType;
   title: React.ReactNode;
-  key: SafeKey;
+  key: Key;
   isStart: boolean[];
   isEnd: boolean[];
 }
 
-export type GetKey<RecordType> = (record: RecordType, index?: number) => SafeKey;
+export type GetKey<RecordType> = (record: RecordType, index?: number) => Key;
 
 export type GetCheckDisabled<RecordType> = (record: RecordType) => boolean;
 
diff --git a/src/util.tsx b/src/util.tsx
index 9b7b3662..5600c3d1 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -11,10 +11,10 @@ import {
   DataNode,
   Direction,
   FlattenNode,
+  Key,
   KeyEntities,
   NodeElement,
   NodeInstance,
-  SafeKey,
 } from './interface';
 import { AllowDrop, TreeProps } from './Tree';
 import TreeNode from './TreeNode';
@@ -22,7 +22,7 @@ import getEntity from './utils/keyUtil';
 
 export { getPosition, isTreeNode } from './utils/treeUtil';
 
-export function arrDel(list: SafeKey[], value: SafeKey) {
+export function arrDel(list: Key[], value: Key) {
   if (!list) return [];
   const clone = list.slice();
   const index = clone.indexOf(value);
@@ -32,7 +32,7 @@ export function arrDel(list: SafeKey[], value: SafeKey) {
   return clone;
 }
 
-export function arrAdd(list: SafeKey[], value: SafeKey) {
+export function arrAdd(list: Key[], value: Key) {
   const clone = (list || []).slice();
   if (clone.indexOf(value) === -1) {
     clone.push(value);
@@ -45,9 +45,9 @@ export function posToArr(pos: string) {
 }
 
 export function getDragChildrenKeys<TreeDataType extends BasicDataNode = DataNode>(
-  dragNodeKey: SafeKey,
+  dragNodeKey: Key,
   keyEntities: KeyEntities<TreeDataType>,
-): SafeKey[] {
+): Key[] {
   // not contains self
   // self for left or right drag
   const dragChildrenKeys = [];
@@ -95,15 +95,15 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
   allowDrop: AllowDrop<TreeDataType>,
   flattenedNodes: FlattenNode<TreeDataType>[],
   keyEntities: KeyEntities<TreeDataType>,
-  expandKeys: SafeKey[],
+  expandKeys: Key[],
   direction: Direction,
 ): {
   dropPosition: -1 | 0 | 1;
   dropLevelOffset: number;
-  dropTargetKey: SafeKey;
+  dropTargetKey: Key;
   dropTargetPos: string;
-  dropContainerKey: SafeKey;
-  dragOverNodeKey: SafeKey;
+  dropContainerKey: Key;
+  dragOverNodeKey: Key;
   dropAllowed: boolean;
 } {
   const { clientX, clientY } = event;
@@ -266,7 +266,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
  * @param props
  * @returns [string]
  */
-export function calcSelectedKeys(selectedKeys: SafeKey[], props: TreeProps) {
+export function calcSelectedKeys(selectedKeys: Key[], props: TreeProps) {
   if (!selectedKeys) return undefined;
 
   const { multiple } = props;
@@ -303,7 +303,7 @@ export function convertDataToTree(
 /**
  * Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style
  */
-export function parseCheckedKeys(keys: SafeKey[] | { checked: SafeKey[]; halfChecked: SafeKey[] }) {
+export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Key[] }) {
   if (!keys) {
     return null;
   }
@@ -334,10 +334,10 @@ export function parseCheckedKeys(keys: SafeKey[] | { checked: SafeKey[]; halfChe
  * @param keyList
  * @param keyEntities
  */
-export function conductExpandParent(keyList: SafeKey[], keyEntities: KeyEntities): SafeKey[] {
-  const expandedKeys = new Set<SafeKey>();
+export function conductExpandParent(keyList: Key[], keyEntities: KeyEntities): Key[] {
+  const expandedKeys = new Set<Key>();
 
-  function conductUp(key: SafeKey) {
+  function conductUp(key: Key) {
     if (expandedKeys.has(key)) return;
 
     const entity = getEntity(keyEntities, key);
diff --git a/src/utils/conductUtil.ts b/src/utils/conductUtil.ts
index 84f2030e..9f1fda9c 100644
--- a/src/utils/conductUtil.ts
+++ b/src/utils/conductUtil.ts
@@ -4,18 +4,18 @@ import {
   DataEntity,
   DataNode,
   GetCheckDisabled,
-  SafeKey,
+  Key,
   KeyEntities,
 } from '../interface';
 import getEntity from './keyUtil';
 
 interface ConductReturnType {
-  checkedKeys: SafeKey[];
-  halfCheckedKeys: SafeKey[];
+  checkedKeys: Key[];
+  halfCheckedKeys: Key[];
 }
 
-function removeFromCheckedKeys(halfCheckedKeys: Set<SafeKey>, checkedKeys: Set<SafeKey>) {
-  const filteredKeys = new Set<SafeKey>();
+function removeFromCheckedKeys(halfCheckedKeys: Set<Key>, checkedKeys: Set<Key>) {
+  const filteredKeys = new Set<Key>();
   halfCheckedKeys.forEach(key => {
     if (!checkedKeys.has(key)) {
       filteredKeys.add(key);
@@ -31,13 +31,13 @@ export function isCheckDisabled<TreeDataType>(node: TreeDataType) {
 
 // Fill miss keys
 function fillConductCheck<TreeDataType extends BasicDataNode = DataNode>(
-  keys: Set<SafeKey>,
+  keys: Set<Key>,
   levelEntities: Map<number, Set<DataEntity<TreeDataType>>>,
   maxLevel: number,
   syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
-  const checkedKeys = new Set<SafeKey>(keys);
-  const halfCheckedKeys = new Set<SafeKey>();
+  const checkedKeys = new Set<Key>(keys);
+  const halfCheckedKeys = new Set<Key>();
 
   // Add checked keys top to bottom
   for (let level = 0; level <= maxLevel; level += 1) {
@@ -56,7 +56,7 @@ function fillConductCheck<TreeDataType extends BasicDataNode = DataNode>(
   }
 
   // Add checked keys from bottom to top
-  const visitedKeys = new Set<SafeKey>();
+  const visitedKeys = new Set<Key>();
   for (let level = maxLevel; level >= 0; level -= 1) {
     const entities = levelEntities.get(level) || new Set();
     entities.forEach(entity => {
@@ -107,14 +107,14 @@ function fillConductCheck<TreeDataType extends BasicDataNode = DataNode>(
 
 // Remove useless key
 function cleanConductCheck<TreeDataType extends BasicDataNode = DataNode>(
-  keys: Set<SafeKey>,
-  halfKeys: SafeKey[],
+  keys: Set<Key>,
+  halfKeys: Key[],
   levelEntities: Map<number, Set<DataEntity<TreeDataType>>>,
   maxLevel: number,
   syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
-  const checkedKeys = new Set<SafeKey>(keys);
-  let halfCheckedKeys = new Set<SafeKey>(halfKeys);
+  const checkedKeys = new Set<Key>(keys);
+  let halfCheckedKeys = new Set<Key>(halfKeys);
 
   // Remove checked keys from top to bottom
   for (let level = 0; level <= maxLevel; level += 1) {
@@ -133,8 +133,8 @@ function cleanConductCheck<TreeDataType extends BasicDataNode = DataNode>(
   }
 
   // Remove checked keys form bottom to top
-  halfCheckedKeys = new Set<SafeKey>();
-  const visitedKeys = new Set<SafeKey>();
+  halfCheckedKeys = new Set<Key>();
+  const visitedKeys = new Set<Key>();
   for (let level = maxLevel; level >= 0; level -= 1) {
     const entities = levelEntities.get(level) || new Set();
 
@@ -191,12 +191,12 @@ function cleanConductCheck<TreeDataType extends BasicDataNode = DataNode>(
  * @param mode `fill` to fill missing key, `clean` to remove useless key
  */
 export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
-  keyList: SafeKey[],
-  checked: true | { checked: false; halfCheckedKeys: SafeKey[] },
+  keyList: Key[],
+  checked: true | { checked: false; halfCheckedKeys: Key[] },
   keyEntities: KeyEntities<TreeDataType>,
   getCheckDisabled?: GetCheckDisabled<TreeDataType>,
 ): ConductReturnType {
-  const warningMissKeys: SafeKey[] = [];
+  const warningMissKeys: Key[] = [];
 
   let syntheticGetCheckDisabled: GetCheckDisabled<TreeDataType>;
   if (getCheckDisabled) {
@@ -206,7 +206,7 @@ export function conductCheck<TreeDataType extends BasicDataNode = DataNode>(
   }
 
   // We only handle exist keys
-  const keys = new Set<SafeKey>(
+  const keys = new Set<Key>(
     keyList.filter(key => {
       const hasEntity = !!getEntity(keyEntities, key);
       if (!hasEntity) {
diff --git a/src/utils/diffUtil.ts b/src/utils/diffUtil.ts
index 5114fc8c..85496435 100644
--- a/src/utils/diffUtil.ts
+++ b/src/utils/diffUtil.ts
@@ -1,6 +1,6 @@
-import { SafeKey, FlattenNode } from '../interface';
+import { Key, FlattenNode } from '../interface';
 
-export function findExpandedKeys(prev: SafeKey[] = [], next: SafeKey[] = []) {
+export function findExpandedKeys(prev: Key[] = [], next: Key[] = []) {
   const prevLen = prev.length;
   const nextLen = next.length;
 
@@ -8,8 +8,8 @@ export function findExpandedKeys(prev: SafeKey[] = [], next: SafeKey[] = []) {
     return { add: false, key: null };
   }
 
-  function find(shorter: SafeKey[], longer: SafeKey[]) {
-    const cache: Map<SafeKey, boolean> = new Map();
+  function find(shorter: Key[], longer: Key[]) {
+    const cache: Map<Key, boolean> = new Map();
     shorter.forEach(key => {
       cache.set(key, true);
     });
@@ -32,7 +32,7 @@ export function findExpandedKeys(prev: SafeKey[] = [], next: SafeKey[] = []) {
   };
 }
 
-export function getExpandRange(shorter: FlattenNode[], longer: FlattenNode[], key: SafeKey) {
+export function getExpandRange(shorter: FlattenNode[], longer: FlattenNode[], key: Key) {
   const shorterStartIndex = shorter.findIndex(data => data.key === key);
   const shorterEndNode = shorter[shorterStartIndex + 1];
   const longerStartIndex = longer.findIndex(data => data.key === key);
diff --git a/src/utils/keyUtil.ts b/src/utils/keyUtil.ts
index 6722c409..ae464e16 100644
--- a/src/utils/keyUtil.ts
+++ b/src/utils/keyUtil.ts
@@ -1,5 +1,5 @@
-import type { KeyEntities, SafeKey } from '../interface';
+import type { Key, KeyEntities, SafeKey } from '../interface';
 
-export default function getEntity<T = any>(keyEntities: KeyEntities<T>, key: SafeKey) {
-  return keyEntities[key];
+export default function getEntity<T = any>(keyEntities: KeyEntities<T>, key: Key) {
+  return keyEntities[key as SafeKey];
 }
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index d0f9babc..701c1b87 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -26,7 +26,7 @@ export function isTreeNode(node: NodeElement) {
   return node && node.type && node.type.isTreeNode;
 }
 
-export function getKey(key: SafeKey, pos: string) {
+export function getKey(key: Key, pos: string) {
   if (key !== null && key !== undefined) {
     return key;
   }
@@ -92,7 +92,7 @@ export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
         const { children, ...rest } = treeNode.props;
 
         const dataNode: DataNode = {
-          key,
+          key: key as Key,
           ...rest,
         };
 
@@ -117,7 +117,7 @@ export function convertTreeToData(rootNodes: React.ReactNode): DataNode[] {
  */
 export function flattenTreeData<TreeDataType extends BasicDataNode = DataNode>(
   treeNodeList: TreeDataType[],
-  expandedKeys: SafeKey[] | true,
+  expandedKeys: Key[] | true,
   fieldNames: FieldNames,
 ): FlattenNode<TreeDataType>[] {
   const {
@@ -198,7 +198,7 @@ export function traverseDataNodes(
     node: DataNode;
     index: number;
     pos: string;
-    key: SafeKey;
+    key: Key;
     parentPos: string | number;
     level: number;
     nodes: DataNode[];
@@ -222,7 +222,7 @@ export function traverseDataNodes(
   const mergeChildrenPropName = childrenPropName || fieldChildren;
 
   // Get keys
-  let syntheticGetKey: (node: DataNode, pos?: string) => SafeKey;
+  let syntheticGetKey: (node: DataNode, pos?: string) => Key;
   if (externalGetKey) {
     if (typeof externalGetKey === 'string') {
       syntheticGetKey = (node: DataNode) => (node as any)[externalGetKey as string];
@@ -331,7 +331,7 @@ export function convertDataToEntities(
       const mergedKey = getKey(key, pos);
 
       posEntities[pos] = entity;
-      keyEntities[mergedKey] = entity;
+      keyEntities[mergedKey as SafeKey] = entity;
 
       // Fill children
       entity.parent = posEntities[parentPos];
@@ -355,13 +355,13 @@ export function convertDataToEntities(
 }
 
 export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = DataNode> {
-  expandedKeys: SafeKey[];
-  selectedKeys: SafeKey[];
-  loadedKeys: SafeKey[];
-  loadingKeys: SafeKey[];
-  checkedKeys: SafeKey[];
-  halfCheckedKeys: SafeKey[];
-  dragOverNodeKey: SafeKey;
+  expandedKeys: Key[];
+  selectedKeys: Key[];
+  loadedKeys: Key[];
+  loadingKeys: Key[];
+  checkedKeys: Key[];
+  halfCheckedKeys: Key[];
+  dragOverNodeKey: Key;
   dropPosition: number;
   keyEntities: KeyEntities<TreeDataType>;
 }
@@ -370,7 +370,7 @@ export interface TreeNodeRequiredProps<TreeDataType extends BasicDataNode = Data
  * Get TreeNode props with Tree props.
  */
 export function getTreeNodeProps<TreeDataType extends BasicDataNode = DataNode>(
-  key: SafeKey,
+  key: Key,
   {
     expandedKeys,
     selectedKeys,

From 515eb3d2954e57d668fac01282f597378e855a58 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 15 Oct 2024 18:48:35 +0800
Subject: [PATCH 359/397] type: use import type (#883)

---
 .husky/pre-commit        |  1 +
 .prettierignore          |  4 ++++
 .prettierrc              |  1 +
 package.json             | 11 +++++++++--
 src/MotionTreeNode.tsx   |  4 ++--
 src/NodeList.tsx         |  4 ++--
 src/Tree.tsx             |  8 ++++----
 src/TreeNode.tsx         | 11 +++--------
 src/contextTypes.ts      |  4 ++--
 src/util.tsx             |  4 ++--
 src/utils/conductUtil.ts |  2 +-
 src/utils/diffUtil.ts    |  2 +-
 src/utils/treeUtil.ts    |  2 +-
 13 files changed, 33 insertions(+), 25 deletions(-)
 create mode 100644 .husky/pre-commit

diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 00000000..af5adff9
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+lint-staged
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
index ecb24d33..21437722 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,3 +1,7 @@
+.doc
+.storybook
+es
+lib
 **/*.svg
 **/*.ejs
 **/*.html
diff --git a/.prettierrc b/.prettierrc
index 7f12e041..b0427819 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -4,6 +4,7 @@
   "singleQuote": true,
   "tabWidth": 2,
   "trailingComma": "all",
+  "proseWrap": "never",
   "printWidth": 100,
   "arrowParens": "avoid"
 }
diff --git a/package.json b/package.json
index 30619f58..8c573d77 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
     "docs:build": "dumi build",
     "docs:deploy": "gh-pages -d dist",
     "compile": "father build && lessc assets/index.less assets/index.css",
+    "prepare": "husky",
     "prepublishOnly": "npm run compile && np --yolo --no-publish",
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
@@ -41,6 +42,9 @@
     "gh-pages": "npm run docs:build && npm run docs:deploy",
     "now-build": "npm run docs:build"
   },
+  "lint-staged": {
+    "*": "prettier --write --ignore-unknown"
+  },
   "peerDependencies": {
     "react": "*",
     "react-dom": "*"
@@ -62,11 +66,14 @@
     "father": "^4.4.0",
     "gh-pages": "^6.1.1",
     "glob": "^7.1.6",
+    "husky": "^9.1.6",
     "less": "^3.11.1",
+    "lint-staged": "^15.2.10",
     "np": "^10.0.5",
+    "prettier": "^3.3.3",
     "rc-dialog": "^8.1.0",
     "rc-test": "^7.0.15",
-    "rc-tooltip": "6.x",
+    "rc-tooltip": "5.x",
     "rc-trigger": "^5.0.7",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
@@ -79,4 +86,4 @@
     "rc-util": "^5.16.1",
     "rc-virtual-list": "^3.5.1"
   }
-}
+}
\ No newline at end of file
diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index b8faad0a..8d651379 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -3,10 +3,10 @@ import CSSMotion from 'rc-motion';
 import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
 import * as React from 'react';
 import { TreeContext } from './contextTypes';
-import { FlattenNode, TreeNodeProps } from './interface';
+import type { FlattenNode, TreeNodeProps } from './interface';
 import TreeNode from './TreeNode';
 import useUnmount from './useUnmount';
-import { getTreeNodeProps, TreeNodeRequiredProps } from './utils/treeUtil';
+import { getTreeNodeProps, type TreeNodeRequiredProps } from './utils/treeUtil';
 
 interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
   active: boolean;
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index f3e18cb9..8ac8f629 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -3,10 +3,10 @@
  */
 
 import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
-import VirtualList, { ListRef } from 'rc-virtual-list';
+import VirtualList, { type ListRef } from 'rc-virtual-list';
 import * as React from 'react';
 import MotionTreeNode from './MotionTreeNode';
-import {
+import type {
   BasicDataNode,
   DataEntity,
   DataNode,
diff --git a/src/Tree.tsx b/src/Tree.tsx
index f8d426ef..bc460584 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -7,15 +7,15 @@ import pickAttrs from 'rc-util/lib/pickAttrs';
 import warning from 'rc-util/lib/warning';
 import * as React from 'react';
 
-import {
+import type {
   NodeDragEventHandler,
   NodeDragEventParams,
   NodeMouseEventHandler,
   NodeMouseEventParams,
-  TreeContext,
 } from './contextTypes';
+import { TreeContext } from './contextTypes';
 import DropIndicator from './DropIndicator';
-import {
+import type {
   BasicDataNode,
   DataNode,
   Direction,
@@ -29,7 +29,7 @@ import {
   SafeKey,
   ScrollTo,
 } from './interface';
-import NodeList, { MOTION_KEY, MotionEntity, NodeListRef } from './NodeList';
+import NodeList, { MOTION_KEY, MotionEntity, type NodeListRef } from './NodeList';
 import TreeNode from './TreeNode';
 import {
   arrAdd,
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 3a51f2cb..a72f2cb1 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -2,9 +2,9 @@ import classNames from 'classnames';
 import pickAttrs from 'rc-util/lib/pickAttrs';
 import * as React from 'react';
 // @ts-ignore
-import { TreeContext, TreeContextProps } from './contextTypes';
+import { TreeContext, type TreeContextProps } from './contextTypes';
 import Indent from './Indent';
-import { TreeNodeProps } from './interface';
+import type { TreeNodeProps } from './interface';
 import getEntity from './utils/keyUtil';
 import { convertNodePropsToEventData } from './utils/treeUtil';
 
@@ -579,12 +579,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
         {...ariaSelected}
         {...dataOrAriaAttributeProps}
       >
-        <Indent
-          prefixCls={prefixCls}
-          level={level}
-          isStart={isStart}
-          isEnd={isEnd}
-        />
+        <Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
         {this.renderDragHandler()}
         {this.renderSwitcher()}
         {this.renderCheckbox()}
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 2132ff26..5543e0fb 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -3,7 +3,7 @@
  * When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
  */
 import * as React from 'react';
-import {
+import type {
   BasicDataNode,
   DataNode,
   Direction,
@@ -13,7 +13,7 @@ import {
   KeyEntities,
   NodeInstance,
 } from './interface';
-import { DraggableConfig } from './Tree';
+import type { DraggableConfig } from './Tree';
 
 export type NodeMouseEventParams<
   TreeDataType extends BasicDataNode = DataNode,
diff --git a/src/util.tsx b/src/util.tsx
index 5600c3d1..c689d028 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -5,7 +5,7 @@
 
 import warning from 'rc-util/lib/warning';
 import React from 'react';
-import {
+import type {
   BasicDataNode,
   DataEntity,
   DataNode,
@@ -16,7 +16,7 @@ import {
   NodeElement,
   NodeInstance,
 } from './interface';
-import { AllowDrop, TreeProps } from './Tree';
+import type { AllowDrop, TreeProps } from './Tree';
 import TreeNode from './TreeNode';
 import getEntity from './utils/keyUtil';
 
diff --git a/src/utils/conductUtil.ts b/src/utils/conductUtil.ts
index 9f1fda9c..bd9a0ff6 100644
--- a/src/utils/conductUtil.ts
+++ b/src/utils/conductUtil.ts
@@ -1,5 +1,5 @@
 import warning from 'rc-util/lib/warning';
-import {
+import type {
   BasicDataNode,
   DataEntity,
   DataNode,
diff --git a/src/utils/diffUtil.ts b/src/utils/diffUtil.ts
index 85496435..8f2372e4 100644
--- a/src/utils/diffUtil.ts
+++ b/src/utils/diffUtil.ts
@@ -1,4 +1,4 @@
-import { Key, FlattenNode } from '../interface';
+import type { Key, FlattenNode } from '../interface';
 
 export function findExpandedKeys(prev: Key[] = [], next: Key[] = []) {
   const prevLen = prev.length;
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 701c1b87..95a2f72f 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -2,7 +2,7 @@ import toArray from 'rc-util/lib/Children/toArray';
 import omit from 'rc-util/lib/omit';
 import warning from 'rc-util/lib/warning';
 import * as React from 'react';
-import {
+import type {
   BasicDataNode,
   DataEntity,
   DataNode,

From 9b5da03ad0acb724c5f7b82d1906ff4c56862a63 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 15 Oct 2024 18:49:08 +0800
Subject: [PATCH 360/397] 5.10.1

---
 package.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 8c573d77..ae5bcfd0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.10.0",
+  "version": "5.10.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"
@@ -86,4 +86,4 @@
     "rc-util": "^5.16.1",
     "rc-virtual-list": "^3.5.1"
   }
-}
\ No newline at end of file
+}

From 37cb6716c967e665ab6759387f27b4dcc91d0469 Mon Sep 17 00:00:00 2001
From: Jony J <1844749591@qq.com>
Date: Mon, 25 Nov 2024 11:19:43 +0800
Subject: [PATCH 361/397] feat: improve a11y (#888)

* feat: improve a11y

* feat: add a11y attributes

* feat: add a11y attributes

* test: update snapshot

* chore: remove some a11y attributes

* feat: improve tree a11y

* chore: remove duplicate attr

* feat: add aria attr

* test: update snapshot
---
 .gitignore                                    |   1 +
 src/NodeList.tsx                              |   1 +
 src/Tree.tsx                                  |   1 -
 src/TreeNode.tsx                              |   9 +-
 tests/__snapshots__/Tree.spec.tsx.snap        | 133 +++++++---
 .../__snapshots__/TreeNodeProps.spec.tsx.snap |  86 ++++---
 tests/__snapshots__/TreeProps.spec.tsx.snap   | 233 ++++++++++++------
 7 files changed, 317 insertions(+), 147 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4126ce56..8aae36ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .storybook
+.vscode
 *.iml
 *.log
 .doc/
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 8ac8f629..bc3a5062 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -326,6 +326,7 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
         itemHeight={itemHeight}
         prefixCls={`${prefixCls}-list`}
         ref={listRef}
+        role="tree"
         onVisibleChange={originList => {
           // The best match is using `fullList` - `originList` = `restList`
           // and check the `restList` to see if has the MOTION_KEY node
diff --git a/src/Tree.tsx b/src/Tree.tsx
index bc460584..050930dd 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -1468,7 +1468,6 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
         }}
       >
         <div
-          role="tree"
           className={classNames(prefixCls, className, rootClassName, {
             [`${prefixCls}-show-line`]: showLine,
             [`${prefixCls}-focused`]: focused,
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index a72f2cb1..d2878dd7 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -353,7 +353,7 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
   // ====================== Render: Checkbox ======================
   // Checkbox
   renderCheckbox = () => {
-    const { checked, halfChecked, disableCheckbox } = this.props;
+    const { checked, halfChecked, disableCheckbox, title } = this.props;
     const {
       context: { prefixCls },
     } = this.props;
@@ -374,6 +374,10 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
           (disabled || disableCheckbox) && `${prefixCls}-checkbox-disabled`,
         )}
         onClick={this.onCheck}
+        role="checkbox"
+        aria-checked={halfChecked ? 'mixed' : checked}
+        aria-disabled={disabled || disableCheckbox}
+        aria-label={`Select ${typeof title === 'string' ? title : 'tree node'}`}
       >
         {$custom}
       </span>
@@ -545,6 +549,8 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
     return (
       <div
         ref={domRef}
+        role="treeitem"
+        aria-expanded={isLeaf ? undefined : expanded}
         className={classNames(className, `${prefixCls}-treenode`, {
           [`${prefixCls}-treenode-disabled`]: disabled,
           [`${prefixCls}-treenode-switcher-${expanded ? 'open' : 'close'}`]: !isLeaf,
@@ -567,7 +573,6 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
         style={style}
         // Draggable config
         draggable={draggableWithoutDisabled}
-        aria-grabbed={dragging}
         onDragStart={draggableWithoutDisabled ? this.onDragStart : undefined}
         // Drop config
         onDragEnter={mergedDraggable ? this.onDragEnter : undefined}
diff --git a/tests/__snapshots__/Tree.spec.tsx.snap b/tests/__snapshots__/Tree.spec.tsx.snap
index a26bd869..0385139a 100644
--- a/tests/__snapshots__/Tree.spec.tsx.snap
+++ b/tests/__snapshots__/Tree.spec.tsx.snap
@@ -3,7 +3,6 @@
 exports[`Tree Basic check basic render 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -28,6 +27,7 @@ exports[`Tree Basic check basic render 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -39,9 +39,10 @@ exports[`Tree Basic check basic render 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -51,7 +52,10 @@ exports[`Tree Basic check basic render 1`] = `
               class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
@@ -68,9 +72,10 @@ exports[`Tree Basic check basic render 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -84,7 +89,11 @@ exports[`Tree Basic check basic render 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-disabled="true"
+              aria-label="Select tree node"
               class="rc-tree-checkbox rc-tree-checkbox-disabled"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -101,9 +110,10 @@ exports[`Tree Basic check basic render 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -117,7 +127,10 @@ exports[`Tree Basic check basic render 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -143,7 +156,6 @@ exports[`Tree Basic check basic render 1`] = `
 exports[`Tree Basic check check after data ready works 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -168,6 +180,7 @@ exports[`Tree Basic check check after data ready works 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -179,9 +192,10 @@ exports[`Tree Basic check check after data ready works 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -191,7 +205,10 @@ exports[`Tree Basic check check after data ready works 1`] = `
               class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
+              aria-checked="true"
+              aria-label="Select Light"
               class="rc-tree-checkbox rc-tree-checkbox-checked"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
@@ -208,9 +225,10 @@ exports[`Tree Basic check check after data ready works 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -224,7 +242,10 @@ exports[`Tree Basic check check after data ready works 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="true"
+              aria-label="Select Bamboo"
               class="rc-tree-checkbox rc-tree-checkbox-checked"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -250,7 +271,6 @@ exports[`Tree Basic check check after data ready works 1`] = `
 exports[`Tree Basic check check update when Tree trigger componentWillReceiveProps 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -275,6 +295,7 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -286,9 +307,10 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -298,7 +320,10 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="true"
+              aria-label="Select parent 1"
               class="rc-tree-checkbox rc-tree-checkbox-checked"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -324,7 +349,6 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
 exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -349,6 +373,7 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -360,9 +385,10 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -386,9 +412,10 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -421,7 +448,6 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
 exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -446,6 +472,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -457,9 +484,10 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -483,9 +511,10 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -509,9 +538,10 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -539,9 +569,10 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -578,7 +609,6 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
 exports[`Tree Basic renders correctly 1`] = `
 <div
   class="rc-tree forTest rc-tree-show-line"
-  role="tree"
 >
   <div>
     <input
@@ -603,6 +633,7 @@ exports[`Tree Basic renders correctly 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -614,9 +645,10 @@ exports[`Tree Basic renders correctly 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="spe rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -626,7 +658,10 @@ exports[`Tree Basic renders correctly 1`] = `
               class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
+              aria-checked="false"
+              aria-label="Select parent 1"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
@@ -643,9 +678,10 @@ exports[`Tree Basic renders correctly 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-open"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -659,7 +695,11 @@ exports[`Tree Basic renders correctly 1`] = `
               class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
+              aria-checked="false"
+              aria-disabled="true"
+              aria-label="Select leaf 1"
               class="rc-tree-checkbox rc-tree-checkbox-disabled"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
@@ -676,9 +716,10 @@ exports[`Tree Basic renders correctly 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -695,7 +736,10 @@ exports[`Tree Basic renders correctly 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-label="Select leaf"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -712,9 +756,10 @@ exports[`Tree Basic renders correctly 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -731,7 +776,10 @@ exports[`Tree Basic renders correctly 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-label="Select leaf"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -748,9 +796,10 @@ exports[`Tree Basic renders correctly 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -764,7 +813,11 @@ exports[`Tree Basic renders correctly 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-disabled="true"
+              aria-label="Select leaf 2"
               class="rc-tree-checkbox rc-tree-checkbox-disabled"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -790,7 +843,6 @@ exports[`Tree Basic renders correctly 1`] = `
 exports[`Tree Basic renders opaque children correctly 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -815,6 +867,7 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -826,9 +879,10 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -861,7 +915,6 @@ exports[`Tree Basic renders opaque children correctly 1`] = `
 exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
 <div
   class="rc-tree root-tree"
-  role="tree"
   style="background-color: cyan;"
 >
   <div>
@@ -887,6 +940,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -898,9 +952,10 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -924,9 +979,10 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -950,9 +1006,10 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -976,9 +1033,10 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1002,9 +1060,10 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1028,9 +1087,10 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1054,9 +1114,10 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1080,9 +1141,10 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1106,9 +1168,10 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
diff --git a/tests/__snapshots__/TreeNodeProps.spec.tsx.snap b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
index b7b5ee63..d8a4b849 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
@@ -3,7 +3,6 @@
 exports[`TreeNode Props className 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -28,6 +27,7 @@ exports[`TreeNode Props className 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -39,9 +39,10 @@ exports[`TreeNode Props className 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -65,9 +66,10 @@ exports[`TreeNode Props className 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -95,9 +97,10 @@ exports[`TreeNode Props className 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -128,9 +131,10 @@ exports[`TreeNode Props className 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -158,9 +162,10 @@ exports[`TreeNode Props className 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -193,7 +198,6 @@ exports[`TreeNode Props className 1`] = `
 exports[`TreeNode Props customize icon component 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -218,6 +222,7 @@ exports[`TreeNode Props customize icon component 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -229,9 +234,10 @@ exports[`TreeNode Props customize icon component 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -268,7 +274,6 @@ exports[`TreeNode Props customize icon component 1`] = `
 exports[`TreeNode Props customize icon element 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -293,6 +298,7 @@ exports[`TreeNode Props customize icon element 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -304,9 +310,10 @@ exports[`TreeNode Props customize icon element 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -343,7 +350,6 @@ exports[`TreeNode Props customize icon element 1`] = `
 exports[`TreeNode Props customize icon hide icon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -368,6 +374,7 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -379,9 +386,10 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -411,7 +419,6 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
 exports[`TreeNode Props data and aria props renders aria attributes on li 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -436,6 +443,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -447,10 +455,11 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             aria-label="0-0"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -474,10 +483,11 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             aria-label="0-0-0"
             class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -505,10 +515,11 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             aria-label="0-0-0-0"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -539,10 +550,11 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             aria-label="0-0-1"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -570,10 +582,11 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             aria-label="0-1"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -606,7 +619,6 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
 exports[`TreeNode Props data and aria props renders data attributes on li 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -631,6 +643,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -642,10 +655,11 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
             data-test="0-0"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -669,10 +683,11 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="tree-node-cls rc-tree-treenode rc-tree-treenode-switcher-open"
             data-test="0-0-0"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -700,10 +715,11 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             data-test="0-0-0-0"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -734,10 +750,11 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             data-test="0-0-1"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -765,10 +782,11 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             data-test="0-1"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -801,7 +819,6 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
 exports[`TreeNode Props isLeaf 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -826,6 +843,7 @@ exports[`TreeNode Props isLeaf 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -837,9 +855,9 @@ exports[`TreeNode Props isLeaf 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -860,9 +878,9 @@ exports[`TreeNode Props isLeaf 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -896,7 +914,6 @@ exports[`TreeNode Props isLeaf 1`] = `
 exports[`TreeNode Props isLeaf 2`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -921,6 +938,7 @@ exports[`TreeNode Props isLeaf 2`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -932,9 +950,9 @@ exports[`TreeNode Props isLeaf 2`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -958,9 +976,9 @@ exports[`TreeNode Props isLeaf 2`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
             class="rc-tree-treenode rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -997,7 +1015,6 @@ exports[`TreeNode Props isLeaf 2`] = `
 exports[`TreeNode Props isLeaf 3`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1022,6 +1039,7 @@ exports[`TreeNode Props isLeaf 3`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1033,9 +1051,10 @@ exports[`TreeNode Props isLeaf 3`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1059,9 +1078,10 @@ exports[`TreeNode Props isLeaf 3`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
diff --git a/tests/__snapshots__/TreeProps.spec.tsx.snap b/tests/__snapshots__/TreeProps.spec.tsx.snap
index 9353689e..0fdc31b9 100644
--- a/tests/__snapshots__/TreeProps.spec.tsx.snap
+++ b/tests/__snapshots__/TreeProps.spec.tsx.snap
@@ -3,7 +3,6 @@
 exports[`Tree Props checkStrictly 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -28,6 +27,7 @@ exports[`Tree Props checkStrictly 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -39,9 +39,10 @@ exports[`Tree Props checkStrictly 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -51,7 +52,10 @@ exports[`Tree Props checkStrictly 1`] = `
               class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
@@ -68,9 +72,10 @@ exports[`Tree Props checkStrictly 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -84,7 +89,10 @@ exports[`Tree Props checkStrictly 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -110,7 +118,6 @@ exports[`Tree Props checkStrictly 1`] = `
 exports[`Tree Props checkable default 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -135,6 +142,7 @@ exports[`Tree Props checkable default 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -146,9 +154,10 @@ exports[`Tree Props checkable default 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -158,7 +167,10 @@ exports[`Tree Props checkable default 1`] = `
               class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
@@ -175,9 +187,10 @@ exports[`Tree Props checkable default 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -191,7 +204,10 @@ exports[`Tree Props checkable default 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -217,7 +233,6 @@ exports[`Tree Props checkable default 1`] = `
 exports[`Tree Props checkable without selectable 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -242,6 +257,7 @@ exports[`Tree Props checkable without selectable 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -253,9 +269,10 @@ exports[`Tree Props checkable without selectable 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -265,7 +282,10 @@ exports[`Tree Props checkable without selectable 1`] = `
               class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
@@ -282,9 +302,10 @@ exports[`Tree Props checkable without selectable 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -298,7 +319,10 @@ exports[`Tree Props checkable without selectable 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -324,7 +348,6 @@ exports[`Tree Props checkable without selectable 1`] = `
 exports[`Tree Props custom switcher icon switcher icon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -349,6 +372,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -360,9 +384,10 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -386,9 +411,10 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -416,9 +442,10 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -446,9 +473,10 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -485,7 +513,6 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
 exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -510,6 +537,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -521,9 +549,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -551,9 +580,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -581,9 +611,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -607,9 +638,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -641,9 +673,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -675,9 +708,10 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -714,7 +748,6 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
 exports[`Tree Props data and aria props renders aria attributes 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -740,6 +773,7 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
   <div
     aria-label="name"
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -759,7 +793,6 @@ exports[`Tree Props data and aria props renders aria attributes 1`] = `
 exports[`Tree Props data and aria props renders data attributes 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -785,6 +818,7 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
   <div
     class="rc-tree-list"
     data-test="tree"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -804,7 +838,6 @@ exports[`Tree Props data and aria props renders data attributes 1`] = `
 exports[`Tree Props defaultExpandAll 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -829,6 +862,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -840,9 +874,10 @@ exports[`Tree Props defaultExpandAll 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -866,9 +901,10 @@ exports[`Tree Props defaultExpandAll 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -905,7 +941,6 @@ exports[`Tree Props defaultExpandAll 1`] = `
 exports[`Tree Props disabled basic 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -931,6 +966,7 @@ exports[`Tree Props disabled basic 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -942,9 +978,10 @@ exports[`Tree Props disabled basic 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -977,7 +1014,6 @@ exports[`Tree Props disabled basic 1`] = `
 exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1003,6 +1039,7 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1014,9 +1051,10 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1049,7 +1087,6 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
 exports[`Tree Props icon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1074,6 +1111,7 @@ exports[`Tree Props icon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1085,9 +1123,10 @@ exports[`Tree Props icon 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1115,9 +1154,10 @@ exports[`Tree Props icon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1158,7 +1198,6 @@ exports[`Tree Props icon 1`] = `
 exports[`Tree Props invalidate checkedKeys null 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1183,6 +1222,7 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1194,9 +1234,10 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1206,7 +1247,10 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
               class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
@@ -1223,9 +1267,10 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1239,7 +1284,10 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -1265,7 +1313,6 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
 exports[`Tree Props invalidate checkedKeys number 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1290,6 +1337,7 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1301,9 +1349,10 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1313,7 +1362,10 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
               class="rc-tree-switcher rc-tree-switcher_open"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-open"
@@ -1330,9 +1382,10 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1346,7 +1399,10 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
               class="rc-tree-switcher rc-tree-switcher-noop"
             />
             <span
+              aria-checked="false"
+              aria-label="Select tree node"
               class="rc-tree-checkbox"
+              role="checkbox"
             />
             <span
               class="rc-tree-node-content-wrapper rc-tree-node-content-wrapper-normal"
@@ -1372,7 +1428,6 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
 exports[`Tree Props loadData basic 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1397,6 +1452,7 @@ exports[`Tree Props loadData basic 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1408,9 +1464,10 @@ exports[`Tree Props loadData basic 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1431,9 +1488,10 @@ exports[`Tree Props loadData basic 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1467,7 +1525,6 @@ exports[`Tree Props loadData basic 1`] = `
 exports[`Tree Props multiple 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1492,6 +1549,7 @@ exports[`Tree Props multiple 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1503,9 +1561,10 @@ exports[`Tree Props multiple 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1529,9 +1588,10 @@ exports[`Tree Props multiple 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1568,7 +1628,6 @@ exports[`Tree Props multiple 1`] = `
 exports[`Tree Props prefixCls 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1593,6 +1652,7 @@ exports[`Tree Props prefixCls 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1612,7 +1672,6 @@ exports[`Tree Props prefixCls 1`] = `
 exports[`Tree Props prefixCls 2`] = `
 <div
   class="test-prefix"
-  role="tree"
 >
   <div>
     <input
@@ -1637,6 +1696,7 @@ exports[`Tree Props prefixCls 2`] = `
   </div>
   <div
     class="test-prefix-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1656,7 +1716,6 @@ exports[`Tree Props prefixCls 2`] = `
 exports[`Tree Props selectable with selectable 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1681,6 +1740,7 @@ exports[`Tree Props selectable with selectable 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1692,9 +1752,10 @@ exports[`Tree Props selectable with selectable 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1718,9 +1779,10 @@ exports[`Tree Props selectable with selectable 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1757,7 +1819,6 @@ exports[`Tree Props selectable with selectable 1`] = `
 exports[`Tree Props selectable without selectable 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1782,6 +1843,7 @@ exports[`Tree Props selectable without selectable 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1793,9 +1855,10 @@ exports[`Tree Props selectable without selectable 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1819,9 +1882,10 @@ exports[`Tree Props selectable without selectable 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1858,7 +1922,6 @@ exports[`Tree Props selectable without selectable 1`] = `
 exports[`Tree Props showIcon 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1883,6 +1946,7 @@ exports[`Tree Props showIcon 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1894,9 +1958,10 @@ exports[`Tree Props showIcon 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1920,9 +1985,10 @@ exports[`Tree Props showIcon 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -1955,7 +2021,6 @@ exports[`Tree Props showIcon 1`] = `
 exports[`Tree Props showIcon 2`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -1980,6 +2045,7 @@ exports[`Tree Props showIcon 2`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -1991,9 +2057,10 @@ exports[`Tree Props showIcon 2`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2014,9 +2081,10 @@ exports[`Tree Props showIcon 2`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2046,7 +2114,6 @@ exports[`Tree Props showIcon 2`] = `
 exports[`Tree Props showIcon 3`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -2071,6 +2138,7 @@ exports[`Tree Props showIcon 3`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -2082,9 +2150,10 @@ exports[`Tree Props showIcon 3`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2112,9 +2181,10 @@ exports[`Tree Props showIcon 3`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2142,9 +2212,10 @@ exports[`Tree Props showIcon 3`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2175,9 +2246,10 @@ exports[`Tree Props showIcon 3`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2205,9 +2277,10 @@ exports[`Tree Props showIcon 3`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2240,7 +2313,6 @@ exports[`Tree Props showIcon 3`] = `
 exports[`Tree Props showLine 1`] = `
 <div
   class="rc-tree rc-tree-show-line"
-  role="tree"
 >
   <div>
     <input
@@ -2265,6 +2337,7 @@ exports[`Tree Props showLine 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -2284,7 +2357,6 @@ exports[`Tree Props showLine 1`] = `
 exports[`Tree Props treeData 1`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -2309,6 +2381,7 @@ exports[`Tree Props treeData 1`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -2320,9 +2393,10 @@ exports[`Tree Props treeData 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2346,9 +2420,10 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2372,9 +2447,10 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2402,9 +2478,10 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="true"
             class="rc-tree-treenode rc-tree-treenode-switcher-open"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2432,9 +2509,10 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2465,9 +2543,10 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2498,9 +2577,10 @@ exports[`Tree Props treeData 1`] = `
             </span>
           </div>
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"
@@ -2537,7 +2617,6 @@ exports[`Tree Props treeData 1`] = `
 exports[`Tree Props treeData 2`] = `
 <div
   class="rc-tree"
-  role="tree"
 >
   <div>
     <input
@@ -2562,6 +2641,7 @@ exports[`Tree Props treeData 2`] = `
   </div>
   <div
     class="rc-tree-list"
+    role="tree"
     style="position: relative;"
   >
     <div
@@ -2573,9 +2653,10 @@ exports[`Tree Props treeData 2`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            aria-grabbed="false"
+            aria-expanded="false"
             class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
             draggable="false"
+            role="treeitem"
           >
             <span
               aria-hidden="true"

From 63195ee74aee3ae7027f8ac826928a6dd255d804 Mon Sep 17 00:00:00 2001
From: Jony J <1844749591@qq.com>
Date: Mon, 2 Dec 2024 15:33:23 +0800
Subject: [PATCH 362/397] feat: add `disabledStrategy` prop to support dynamic
 node disabled state calculation (#890)

* feat: add internal disabledStrategy for tree-select

* fix: case fix

* refactor: move disabledStrategy to InternalContext

* test: add test case for nodeDisabled

* chore: adjust code style
---
 src/TreeNode.tsx    |  16 +++++--
 src/contextTypes.ts |   5 +++
 src/index.ts        |   3 +-
 src/interface.tsx   |   5 ++-
 tests/Tree.spec.tsx | 103 +++++++++++++++++++++++++++++++++++++++++---
 5 files changed, 121 insertions(+), 11 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index d2878dd7..bb29b9d8 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -2,7 +2,7 @@ import classNames from 'classnames';
 import pickAttrs from 'rc-util/lib/pickAttrs';
 import * as React from 'react';
 // @ts-ignore
-import { TreeContext, type TreeContextProps } from './contextTypes';
+import { TreeContext, UnstableContext, type TreeContextProps } from './contextTypes';
 import Indent from './Indent';
 import type { TreeNodeProps } from './interface';
 import getEntity from './utils/keyUtil';
@@ -17,6 +17,7 @@ export type { TreeNodeProps } from './interface';
 
 export interface InternalTreeNodeProps extends TreeNodeProps {
   context?: TreeContextProps;
+  unstableContext?: React.ContextType<typeof UnstableContext>;
 }
 
 export interface TreeNodeState {
@@ -231,12 +232,13 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
   };
 
   isDisabled = () => {
-    const { disabled } = this.props;
+    const { disabled, data } = this.props;
     const {
       context: { disabled: treeDisabled },
+      unstableContext: { nodeDisabled },
     } = this.props;
 
-    return !!(treeDisabled || disabled);
+    return !!(treeDisabled || disabled || (nodeDisabled && nodeDisabled(data)));
   };
 
   isCheckable = () => {
@@ -596,7 +598,13 @@ class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeSt
 
 const ContextTreeNode: React.FC<TreeNodeProps> = props => (
   <TreeContext.Consumer>
-    {context => <InternalTreeNode {...props} context={context} />}
+    {context => (
+      <UnstableContext.Consumer>
+        {unstableContext => (
+          <InternalTreeNode {...props} context={context} unstableContext={unstableContext} />
+        )}
+      </UnstableContext.Consumer>
+    )}
   </TreeContext.Consumer>
 );
 
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 5543e0fb..6f99ce38 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -93,3 +93,8 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
 }
 
 export const TreeContext: React.Context<TreeContextProps<any> | null> = React.createContext(null);
+
+/** Internal usage, safe to remove. Do not use in prod */
+export const UnstableContext = React.createContext<{
+  nodeDisabled?: (node: DataNode) => boolean;
+}>({});
diff --git a/src/index.ts b/src/index.ts
index 57929b51..7f8954ac 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,7 +2,8 @@ import Tree from './Tree';
 import TreeNode from './TreeNode';
 import type { TreeProps } from './Tree';
 import type { TreeNodeProps, BasicDataNode, FieldDataNode } from './interface';
+import { UnstableContext } from './contextTypes';
 
-export { TreeNode };
+export { TreeNode, UnstableContext };
 export type { TreeProps, TreeNodeProps, BasicDataNode, FieldDataNode };
 export default Tree;
diff --git a/src/interface.tsx b/src/interface.tsx
index 6c5e5ef4..f3d2ec5d 100644
--- a/src/interface.tsx
+++ b/src/interface.tsx
@@ -69,7 +69,10 @@ export type Key = React.Key;
  */
 export type SafeKey = Exclude<Key, bigint>;
 
-export type KeyEntities<DateType extends BasicDataNode = any> = Record<SafeKey, DataEntity<DateType>>;
+export type KeyEntities<DateType extends BasicDataNode = any> = Record<
+  SafeKey,
+  DataEntity<DateType>
+>;
 
 export type DataNode = FieldDataNode<{
   key: Key;
diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index d59fc31b..66d95185 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -1,11 +1,12 @@
 /* eslint-disable no-undef, react/no-multi-comp, no-console,
 react/no-unused-state, react/prop-types, no-return-assign */
 import React from 'react';
-import {render, fireEvent, act} from '@testing-library/react';
+import { render, fireEvent, act } from '@testing-library/react';
 import { resetWarned } from 'rc-util/lib/warning';
 import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
 import Tree, { TreeNode } from '../src';
 import { objectMatcher, spyConsole, spyError } from './util';
+import { UnstableContext } from '../src';
 
 const OPEN_CLASSNAME = 'rc-tree-switcher_open';
 const CHECKED_CLASSNAME = 'rc-tree-checkbox-checked';
@@ -1055,9 +1056,9 @@ describe('Tree Basic', () => {
       render(<Tree ref={treeRef} />);
 
       act(() => {
-        treeRef.current.scrollTo({key: 'light', align: 'top'});
+        treeRef.current.scrollTo({ key: 'light', align: 'top' });
       });
-      
+
       jest.runAllTimers();
 
       expect(called).toBeTruthy();
@@ -1092,7 +1093,7 @@ describe('Tree Basic', () => {
       }
 
       const treeRef = React.createRef<any>();
-      
+
       const { container } = render(
         <Tree ref={treeRef} treeData={data} virtual itemHeight={20} height={100} />,
       );
@@ -1101,7 +1102,7 @@ describe('Tree Basic', () => {
         treeRef.current.scrollTo({ key: 5, offset: 5 });
         jest.runAllTimers();
       });
-      
+
       expect(container.querySelector('.rc-tree-list-holder').scrollTop).toEqual(25);
 
       jest.useRealTimers();
@@ -1237,4 +1238,96 @@ describe('Tree Basic', () => {
     expect(container.firstChild).toHaveClass('root-tree');
     expect(container.firstChild).toHaveStyle({ backgroundColor: 'cyan' });
   });
+
+  describe('nodeDisabled', () => {
+    it('should disable node by nodeDisabled function', () => {
+      const { getByRole } = render(
+        <UnstableContext.Provider
+          value={{
+            nodeDisabled: node => node.key === '0-0-0',
+          }}
+        >
+          <Tree defaultExpandAll>
+            <TreeNode title="parent 1" key="0-0">
+              <TreeNode title="leaf 1" key="0-0-0" />
+              <TreeNode title="leaf 2" key="0-0-1" />
+            </TreeNode>
+          </Tree>
+        </UnstableContext.Provider>,
+      );
+
+      expect(getByRole('treeitem', { name: 'leaf 1' })).toHaveClass('rc-tree-treenode-disabled');
+      expect(getByRole('treeitem', { name: 'leaf 2' })).not.toHaveClass(
+        'rc-tree-treenode-disabled',
+      );
+    });
+
+    it('should work with checkable tree', () => {
+      const onCheck = jest.fn();
+      const { container } = render(
+        <UnstableContext.Provider
+          value={{
+            nodeDisabled: node => node.key === '0-0-0',
+          }}
+        >
+          <Tree checkable defaultExpandAll onCheck={onCheck}>
+            <TreeNode title="parent 1" key="0-0">
+              <TreeNode title="leaf 1" key="0-0-0" />
+              <TreeNode title="leaf 2" key="0-0-1" />
+            </TreeNode>
+          </Tree>
+        </UnstableContext.Provider>,
+      );
+
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[1]);
+      expect(onCheck).not.toHaveBeenCalled();
+
+      fireEvent.click(container.querySelectorAll('.rc-tree-checkbox')[2]);
+      expect(onCheck).toHaveBeenCalled();
+    });
+
+    it('should work with selectable tree', () => {
+      const onSelect = jest.fn();
+      const { container } = render(
+        <UnstableContext.Provider
+          value={{
+            nodeDisabled: node => node.key === '0-0-0',
+          }}
+        >
+          <Tree selectable defaultExpandAll onSelect={onSelect}>
+            <TreeNode title="parent 1" key="0-0">
+              <TreeNode title="leaf 1" key="0-0-0" />
+              <TreeNode title="leaf 2" key="0-0-1" />
+            </TreeNode>
+          </Tree>
+        </UnstableContext.Provider>,
+      );
+
+      fireEvent.click(container.querySelectorAll('.rc-tree-node-content-wrapper')[1]);
+      expect(onSelect).not.toHaveBeenCalled();
+
+      fireEvent.click(container.querySelectorAll('.rc-tree-node-content-wrapper')[2]);
+      expect(onSelect).toHaveBeenCalled();
+    });
+
+    it('should override disabled prop', () => {
+      const { getByRole } = render(
+        <UnstableContext.Provider
+          value={{
+            nodeDisabled: node => node.key === '0-0-0',
+          }}
+        >
+          <Tree defaultExpandAll>
+            <TreeNode title="parent 1" key="0-0">
+              <TreeNode title="leaf 1" key="0-0-0" disabled={false} />
+              <TreeNode title="leaf 2" key="0-0-1" disabled />
+            </TreeNode>
+          </Tree>
+        </UnstableContext.Provider>,
+      );
+
+      expect(getByRole('treeitem', { name: 'leaf 1' })).toHaveClass('rc-tree-treenode-disabled');
+      expect(getByRole('treeitem', { name: 'leaf 2' })).toHaveClass('rc-tree-treenode-disabled');
+    });
+  });
 });

From 68f894067c493ae63f7dd9a54516c283aaf068cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 2 Dec 2024 15:39:04 +0800
Subject: [PATCH 363/397] 5.11.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index ae5bcfd0..0088b4da 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.10.1",
+  "version": "5.11.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 4e82422488a183d4d9e10ecdeadbc88988e08bf4 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 24 Dec 2024 06:07:37 +0800
Subject: [PATCH 364/397] chore(deps-dev): bump @types/react from 18.3.14 to
 19.0.1 (#899)

Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.3.14 to 19.0.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0088b4da..087f8e54 100644
--- a/package.json
+++ b/package.json
@@ -55,7 +55,7 @@
     "@testing-library/react": "^15.0.7",
     "@types/jest": "^29.5.10",
     "@types/node": "^22.7.3",
-    "@types/react": "^18.0.0",
+    "@types/react": "^19.0.1",
     "@types/react-dom": "^18.2.17",
     "@types/warning": "^3.0.0",
     "@umijs/fabric": "^4.0.1",

From b478c40122a23c28b82a3c889dc7fa924233ecd8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 24 Dec 2024 06:08:22 +0800
Subject: [PATCH 365/397] chore(deps-dev): bump @testing-library/react from
 15.0.7 to 16.1.0 (#897)

Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 15.0.7 to 16.1.0.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v15.0.7...v16.1.0)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 087f8e54..cfe409ca 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
   "devDependencies": {
     "@rc-component/father-plugin": "^1.0.2",
     "@testing-library/jest-dom": "^6.1.5",
-    "@testing-library/react": "^15.0.7",
+    "@testing-library/react": "^16.1.0",
     "@types/jest": "^29.5.10",
     "@types/node": "^22.7.3",
     "@types/react": "^19.0.1",

From 7c96f633276a8706965c0a103d4b4da66c6028cc Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 24 Dec 2024 06:11:58 +0800
Subject: [PATCH 366/397] chore(deps-dev): bump @types/react-dom from 18.3.2 to
 19.0.1 (#898)

Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.3.2 to 19.0.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: lijianan <574980606@qq.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index cfe409ca..20df3def 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
     "@types/jest": "^29.5.10",
     "@types/node": "^22.7.3",
     "@types/react": "^19.0.1",
-    "@types/react-dom": "^18.2.17",
+    "@types/react-dom": "^19.0.1",
     "@types/warning": "^3.0.0",
     "@umijs/fabric": "^4.0.1",
     "dumi": "^2.1.0",

From 0c75c6379c53f142ed145b688e039772ffa94db8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 24 Dec 2024 06:12:11 +0800
Subject: [PATCH 367/397] chore(deps-dev): bump eslint-plugin-unicorn from
 53.0.0 to 56.0.1 (#889)

Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 53.0.0 to 56.0.1.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v53.0.0...v56.0.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-unicorn
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 20df3def..a2da2165 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,7 @@
     "dumi": "^2.1.0",
     "eslint": "^8.55.0",
     "eslint-plugin-jest": "^28.8.3",
-    "eslint-plugin-unicorn": "^53.0.0",
+    "eslint-plugin-unicorn": "^56.0.1",
     "father": "^4.4.0",
     "gh-pages": "^6.1.1",
     "glob": "^7.1.6",

From 73bf2c0f2a812637ddf23ec01de364e195f8b633 Mon Sep 17 00:00:00 2001
From: kvkto <150343273+kvkto@users.noreply.github.com>
Date: Mon, 30 Dec 2024 03:09:18 +0000
Subject: [PATCH 368/397] feat: horizontal scrolling support in virtual mode
 (#878)

* Horizontal scrolling support in virtual mode

* add test

* small fixes

---------

Co-authored-by: Ivan Kositsin <ivan.kositsin@reltio.com>
---
 src/NodeList.tsx           |  3 +++
 src/Tree.tsx               |  3 +++
 tests/Tree.spec.tsx        | 18 ------------------
 tests/TreeVirtual.spec.tsx | 36 ++++++++++++++++++++++++++++++++++++
 4 files changed, 42 insertions(+), 18 deletions(-)
 create mode 100644 tests/TreeVirtual.spec.tsx

diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index bc3a5062..a2ef90d1 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -92,6 +92,7 @@ interface NodeListProps<TreeDataType extends BasicDataNode> {
   height: number;
   itemHeight: number;
   virtual?: boolean;
+  scrollWidth?: number;
 
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
@@ -158,6 +159,7 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
     height,
     itemHeight,
     virtual,
+    scrollWidth,
 
     focusable,
     activeItem,
@@ -324,6 +326,7 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
         fullHeight={false}
         virtual={virtual}
         itemHeight={itemHeight}
+        scrollWidth={scrollWidth}
         prefixCls={`${prefixCls}-list`}
         ref={listRef}
         role="tree"
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 050930dd..ca966e2a 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -186,6 +186,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   // Virtual List
   height?: number;
   itemHeight?: number;
+  scrollWidth?: number;
   itemScrollOffset?: number;
   virtual?: boolean;
 
@@ -1394,6 +1395,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       filterTreeNode,
       height,
       itemHeight,
+      scrollWidth,
       virtual,
       titleRender,
       dropIndicatorRender,
@@ -1500,6 +1502,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
             onListChangeEnd={this.onListChangeEnd}
             onContextMenu={onContextMenu}
             onScroll={onScroll}
+            scrollWidth={scrollWidth}
             {...this.getTreeNodeRequiredProps()}
             {...domProps}
           />
diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index 66d95185..0ee77750 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -1150,24 +1150,6 @@ describe('Tree Basic', () => {
     ).toHaveLength(2);
   });
 
-  it('support virtual', () => {
-    const data = [];
-    for (let i = 0; i < 99; i += 1) {
-      data.push({
-        key: i,
-        title: i,
-      });
-    }
-
-    const { container } = render(
-      <Tree itemHeight={10} height={100} treeData={data} virtual={false} />,
-    );
-
-    expect(
-      container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode'),
-    ).toHaveLength(99);
-  });
-
   // https://github.com/ant-design/ant-design/issues/28349
   it('should not trigger expend when loading data', () => {
     const then = jest.fn(() => Promise.resolve());
diff --git a/tests/TreeVirtual.spec.tsx b/tests/TreeVirtual.spec.tsx
new file mode 100644
index 00000000..6d241588
--- /dev/null
+++ b/tests/TreeVirtual.spec.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import {render} from '@testing-library/react';
+import Tree from '../src';
+
+jest.mock('rc-virtual-list', () => jest.requireActual('rc-virtual-list'));
+
+describe('Tree Virtual', () => {
+  it('should display all nodes when for nonvirtual tree', () => {
+    const data = [];
+    for (let i = 0; i < 99; i += 1) {
+      data.push({
+        key: i,
+        title: i,
+      });
+    }
+
+    const { container } = render(
+      <Tree itemHeight={10} height={100} treeData={data} virtual={false} />,
+    );
+
+    expect(
+      container.querySelector('.rc-tree-list-holder').querySelectorAll('.rc-tree-treenode'),
+    ).toHaveLength(99);
+  });
+
+  it('should support scrollWidth parameter and show horizontal scroll', () => {  
+    const data = [{key: 1, title: 'title'}];
+    const { container } = render(
+      <Tree treeData={data} virtual={true} scrollWidth={400} itemHeight={20} height={100} />,
+    );
+    
+    expect(container.querySelector('.rc-tree-list-scrollbar-horizontal')).toBeInTheDocument();
+  
+    jest.useRealTimers();
+  });
+});

From d5964b68c3208a72d8343ec75f42dc320e4f33a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Mon, 30 Dec 2024 11:23:31 +0800
Subject: [PATCH 369/397] 5.12.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a2da2165..71c8d74b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.11.0",
+  "version": "5.12.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From f44b340f5accb28c2b116d51a95b6ec1432489e1 Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Tue, 31 Dec 2024 21:12:16 +0800
Subject: [PATCH 370/397] chore: code optimization (#903)

---
 src/DropIndicator.tsx  | 25 +++++++++++++++----------
 src/MotionTreeNode.tsx | 20 +++++++++-----------
 src/NodeList.tsx       |  4 +++-
 src/Tree.tsx           |  9 ++-------
 src/TreeNode.tsx       |  4 +++-
 src/useUnmount.ts      |  4 +++-
 6 files changed, 35 insertions(+), 31 deletions(-)

diff --git a/src/DropIndicator.tsx b/src/DropIndicator.tsx
index 3f519fe1..ec03f990 100644
--- a/src/DropIndicator.tsx
+++ b/src/DropIndicator.tsx
@@ -1,14 +1,13 @@
-import * as React from 'react'
+import React from 'react';
 
-export default function DropIndicator ({
-  dropPosition,
-  dropLevelOffset,
-  indent,
-}: {
-  dropPosition: -1 | 0 | 1,
-  dropLevelOffset: number,
-  indent: number,
-}) {
+export interface DropIndicatorProps {
+  dropPosition: -1 | 0 | 1;
+  dropLevelOffset: number;
+  indent: number;
+}
+
+const DropIndicator: React.FC<Readonly<DropIndicatorProps>> = props => {
+  const { dropPosition, dropLevelOffset, indent } = props;
   const style: React.CSSProperties = {
     pointerEvents: 'none',
     position: 'absolute',
@@ -31,4 +30,10 @@ export default function DropIndicator ({
       break;
   }
   return <div style={style} />;
+};
+
+if (process.env.NODE_ENV !== 'production') {
+  DropIndicator.displayName = 'DropIndicator';
 }
+
+export default DropIndicator;
diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 8d651379..882c56fb 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -19,8 +19,8 @@ interface MotionTreeNodeProps extends Omit<TreeNodeProps, 'domRef'> {
   treeNodeRequiredProps: TreeNodeRequiredProps;
 }
 
-const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeNodeProps> = (
-  {
+const MotionTreeNode = React.forwardRef<HTMLDivElement, MotionTreeNodeProps>((oriProps, ref) => {
+  const {
     className,
     style,
     motion,
@@ -31,9 +31,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
     active,
     treeNodeRequiredProps,
     ...props
-  },
-  ref,
-) => {
+  } = oriProps;
   const [visible, setVisible] = React.useState(true);
   const { prefixCls } = React.useContext(TreeContext);
 
@@ -89,7 +87,7 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
             className={classNames(`${prefixCls}-treenode-motion`, motionClassName)}
             style={motionStyle}
           >
-            {motionNodes.map((treeNode: FlattenNode) => {
+            {motionNodes.map(treeNode => {
               const {
                 data: { ...restProps },
                 title,
@@ -120,10 +118,10 @@ const MotionTreeNode: React.ForwardRefRenderFunction<HTMLDivElement, MotionTreeN
     );
   }
   return <TreeNode domRef={ref} className={className} style={style} {...props} active={active} />;
-};
+});
 
-MotionTreeNode.displayName = 'MotionTreeNode';
-
-const RefMotionTreeNode = React.forwardRef(MotionTreeNode);
+if (process.env.NODE_ENV !== 'production') {
+  MotionTreeNode.displayName = 'MotionTreeNode';
+}
 
-export default RefMotionTreeNode;
+export default MotionTreeNode;
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index a2ef90d1..2106ad55 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -382,6 +382,8 @@ const NodeList = React.forwardRef<NodeListRef, NodeListProps<any>>((props, ref)
   );
 });
 
-NodeList.displayName = 'NodeList';
+if (process.env.NODE_ENV !== 'production') {
+  NodeList.displayName = 'NodeList';
+}
 
 export default NodeList;
diff --git a/src/Tree.tsx b/src/Tree.tsx
index ca966e2a..7f42fe3f 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -15,6 +15,7 @@ import type {
 } from './contextTypes';
 import { TreeContext } from './contextTypes';
 import DropIndicator from './DropIndicator';
+import type { DropIndicatorProps } from './DropIndicator';
 import type {
   BasicDataNode,
   DataNode,
@@ -113,13 +114,7 @@ export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   selectedKeys?: Key[];
   allowDrop?: AllowDrop<TreeDataType>;
   titleRender?: (node: TreeDataType) => React.ReactNode;
-  dropIndicatorRender?: (props: {
-    dropPosition: -1 | 0 | 1;
-    dropLevelOffset: number;
-    indent: number;
-    prefixCls: string;
-    direction: Direction;
-  }) => React.ReactNode;
+  dropIndicatorRender?: (props: DropIndicatorProps) => React.ReactNode;
   onFocus?: React.FocusEventHandler<HTMLDivElement>;
   onBlur?: React.FocusEventHandler<HTMLDivElement>;
   onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index bb29b9d8..ee388798 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -608,7 +608,9 @@ const ContextTreeNode: React.FC<TreeNodeProps> = props => (
   </TreeContext.Consumer>
 );
 
-ContextTreeNode.displayName = 'TreeNode';
+if (process.env.NODE_ENV !== 'production') {
+  ContextTreeNode.displayName = 'TreeNode';
+}
 
 (ContextTreeNode as any).isTreeNode = 1;
 
diff --git a/src/useUnmount.ts b/src/useUnmount.ts
index 2c1ddc4f..a07b4161 100644
--- a/src/useUnmount.ts
+++ b/src/useUnmount.ts
@@ -4,7 +4,7 @@ import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
 /**
  * Trigger only when component unmount
  */
-export default function useUnmount(triggerStart: VoidFunction, triggerEnd: VoidFunction) {
+function useUnmount(triggerStart: VoidFunction, triggerEnd: VoidFunction) {
   const [firstMount, setFirstMount] = React.useState(false);
 
   useLayoutEffect(() => {
@@ -25,3 +25,5 @@ export default function useUnmount(triggerStart: VoidFunction, triggerEnd: VoidF
     };
   }, []);
 }
+
+export default useUnmount;

From ade840a9aad753dcdb51aae41c4a1eef8cd827ee Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Tue, 31 Dec 2024 22:49:49 +0800
Subject: [PATCH 371/397] 5.12.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 71c8d74b..3c12f56a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.12.0",
+  "version": "5.12.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 8fffa7c36f02d978e403d30e0cd5ab34178fad76 Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Thu, 2 Jan 2025 05:33:01 +0800
Subject: [PATCH 372/397] chore: code optimization (#906)

---
 src/Tree.tsx        | 165 +++++++++++++++++++-------------------------
 src/contextTypes.ts |  10 ++-
 src/util.tsx        |   2 +-
 3 files changed, 77 insertions(+), 100 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 7f42fe3f..d5d99e28 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -230,10 +230,9 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
   fieldNames: FieldNames;
 }
 
-class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends React.Component<
-  TreeProps<TreeDataType>,
-  TreeState<TreeDataType>
-> {
+class Tree<
+  TreeDataType extends DataNode | BasicDataNode = DataNode | BasicDataNode,
+> extends React.Component<TreeProps<TreeDataType>, TreeState<TreeDataType>> {
   static defaultProps = {
     prefixCls: 'rc-tree',
     showLine: false,
@@ -419,7 +418,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     // ================ flattenNodes =================
     if (treeData || newState.expandedKeys) {
-      const flattenNodes: FlattenNode[] = flattenTreeData(
+      const flattenNodes = flattenTreeData<DataNode>(
         treeData || prevState.treeData,
         newState.expandedKeys || prevState.expandedKeys,
         fieldNames,
@@ -438,7 +437,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     // ================= checkedKeys =================
     if (props.checkable) {
-      let checkedKeyEntity;
+      let checkedKeyEntity: { checkedKeys?: Key[]; halfCheckedKeys?: Key[] };
 
       if (needSync('checkedKeys')) {
         checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || {};
@@ -545,7 +544,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     if (
       // don't allow drop inside its children
-      dragChildrenKeys.indexOf(dropTargetKey) !== -1 ||
+      dragChildrenKeys.includes(dropTargetKey) ||
       // don't allow drop when drop is not allowed caculated by calcDropPosition
       !dropAllowed
     ) {
@@ -582,7 +581,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
         }
 
         onExpand?.(newExpandedKeys, {
-          node: convertNodePropsToEventData(node.props),
+          node: convertNodePropsToEventData<TreeDataType>(node.props),
           expanded: true,
           nativeEvent: event.nativeEvent,
         });
@@ -608,7 +607,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     onDragEnter?.({
       event,
-      node: convertNodePropsToEventData(node.props),
+      node: convertNodePropsToEventData<TreeDataType>(node.props),
       expandedKeys,
     });
   };
@@ -627,10 +626,10 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       dropLevelOffset,
       dropTargetKey,
       dropContainerKey,
-      dropAllowed,
       dropTargetPos,
+      dropAllowed,
       dragOverNodeKey,
-    } = calcDropPosition(
+    } = calcDropPosition<TreeDataType>(
       event,
       dragNode,
       node,
@@ -643,7 +642,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       direction,
     );
 
-    if (dragChildrenKeys.indexOf(dropTargetKey) !== -1 || !dropAllowed) {
+    if (dragChildrenKeys.includes(dropTargetKey) || !dropAllowed) {
       // don't allow drop inside its children
       // don't allow drop when drop is not allowed calculated by calcDropPosition
       return;
@@ -687,7 +686,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       });
     }
 
-    onDragOver?.({ event, node: convertNodePropsToEventData(node.props) });
+    onDragOver?.({ event, node: convertNodePropsToEventData<TreeDataType>(node.props) });
   };
 
   onNodeDragLeave: NodeDragEventHandler<TreeDataType> = (event, node) => {
@@ -729,7 +728,11 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     window.removeEventListener('dragend', this.onWindowDragEnd);
   };
 
-  onNodeDrop = (event: React.DragEvent<HTMLDivElement>, node, outsideTree: boolean = false) => {
+  onNodeDrop = (
+    event: React.DragEvent<HTMLDivElement>,
+    _: NodeInstance<TreeDataType>,
+    outsideTree: boolean = false,
+  ) => {
     const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } =
       this.state;
 
@@ -749,7 +752,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       active: this.getActiveItem()?.key === dropTargetKey,
       data: getEntity(this.state.keyEntities, dropTargetKey).node,
     };
-    const dropToChild = dragChildrenKeys.indexOf(dropTargetKey) !== -1;
+    const dropToChild = dragChildrenKeys.includes(dropTargetKey);
 
     warning(
       !dropToChild,
@@ -760,7 +763,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
     const dropResult = {
       event,
-      node: convertNodePropsToEventData(abstractDropNodeProps),
+      node: convertNodePropsToEventData<TreeDataType>(abstractDropNodeProps),
       dragNode: this.dragNode ? convertNodePropsToEventData(this.dragNode.props) : null,
       dragNodesKeys: [this.dragNode.props.eventKey].concat(dragChildrenKeys),
       dropToGap: dropPosition !== 0,
@@ -866,7 +869,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
         return entity.node;
       })
-      .filter(node => node);
+      .filter(Boolean);
 
     this.setUncontrolledState({ selectedKeys });
 
@@ -893,7 +896,8 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     const { key } = treeNode;
 
     // Prepare trigger arguments
-    let checkedObj;
+    let checkedObj: { checked: Key[]; halfChecked: Key[] } | React.Key[];
+
     const eventObj: Partial<CheckInfo<TreeDataType>> = {
       event: 'check',
       node: treeNode,
@@ -908,7 +912,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
 
       eventObj.checkedNodes = checkedKeys
         .map(checkedKey => getEntity(keyEntities, checkedKey))
-        .filter(entity => entity)
+        .filter(Boolean)
         .map(entity => entity.node);
 
       this.setUncontrolledState({ checkedKeys });
@@ -948,15 +952,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
         eventObj.checkedNodesPositions.push({ node, pos });
       });
 
-      this.setUncontrolledState(
-        {
-          checkedKeys,
-        },
-        false,
-        {
-          halfCheckedKeys,
-        },
-      );
+      this.setUncontrolledState({ checkedKeys }, false, { halfCheckedKeys });
     }
 
     onCheck?.(checkedObj, eventObj as CheckInfo<TreeDataType>);
@@ -977,7 +973,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       this.setState(({ loadedKeys = [], loadingKeys = [] }): any => {
         const { loadData, onLoad } = this.props;
 
-        if (!loadData || loadedKeys.indexOf(key) !== -1 || loadingKeys.indexOf(key) !== -1) {
+        if (!loadData || loadedKeys.includes(key) || loadingKeys.includes(key)) {
           return null;
         }
 
@@ -1102,19 +1098,8 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
   /** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
   setExpandedKeys = (expandedKeys: Key[]) => {
     const { treeData, fieldNames } = this.state;
-
-    const flattenNodes: FlattenNode<TreeDataType>[] = flattenTreeData<TreeDataType>(
-      treeData,
-      expandedKeys,
-      fieldNames,
-    );
-    this.setUncontrolledState(
-      {
-        expandedKeys,
-        flattenNodes,
-      },
-      true,
-    );
+    const flattenNodes = flattenTreeData<TreeDataType>(treeData, expandedKeys, fieldNames);
+    this.setUncontrolledState({ expandedKeys, flattenNodes }, true);
   };
 
   onNodeExpand = (e: React.MouseEvent<HTMLDivElement>, treeNode: EventDataNode<TreeDataType>) => {
@@ -1130,19 +1115,15 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     }
 
     // Update selected keys
-    const index = expandedKeys.indexOf(key);
+    const certain = expandedKeys.includes(key);
     const targetExpanded = !expanded;
 
     warning(
-      (expanded && index !== -1) || (!expanded && index === -1),
+      (expanded && certain) || (!expanded && !certain),
       'Expand state not sync with index check',
     );
 
-    if (targetExpanded) {
-      expandedKeys = arrAdd(expandedKeys, key);
-    } else {
-      expandedKeys = arrDel(expandedKeys, key);
-    }
+    expandedKeys = targetExpanded ? arrAdd(expandedKeys, key) : arrDel(expandedKeys, key);
 
     this.setExpandedKeys(expandedKeys);
 
@@ -1400,6 +1381,7 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       rootClassName,
       rootStyle,
     } = this.props;
+
     const domProps: React.HTMLAttributes<HTMLDivElement> = pickAttrs(this.props, {
       aria: true,
       data: true,
@@ -1419,51 +1401,48 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       }
     }
 
+    const contextValue = {
+      prefixCls,
+      selectable,
+      showIcon,
+      icon,
+      switcherIcon,
+      draggable: draggableConfig,
+      draggingNodeKey,
+      checkable,
+      checkStrictly,
+      disabled,
+      keyEntities,
+      dropLevelOffset,
+      dropContainerKey,
+      dropTargetKey,
+      dropPosition,
+      dragOverNodeKey,
+      indent,
+      direction,
+      dropIndicatorRender,
+      loadData,
+      filterTreeNode,
+      titleRender,
+      onNodeClick: this.onNodeClick,
+      onNodeDoubleClick: this.onNodeDoubleClick,
+      onNodeExpand: this.onNodeExpand,
+      onNodeSelect: this.onNodeSelect,
+      onNodeCheck: this.onNodeCheck,
+      onNodeLoad: this.onNodeLoad,
+      onNodeMouseEnter: this.onNodeMouseEnter,
+      onNodeMouseLeave: this.onNodeMouseLeave,
+      onNodeContextMenu: this.onNodeContextMenu,
+      onNodeDragStart: this.onNodeDragStart,
+      onNodeDragEnter: this.onNodeDragEnter,
+      onNodeDragOver: this.onNodeDragOver,
+      onNodeDragLeave: this.onNodeDragLeave,
+      onNodeDragEnd: this.onNodeDragEnd,
+      onNodeDrop: this.onNodeDrop,
+    };
+
     return (
-      <TreeContext.Provider
-        value={{
-          prefixCls,
-          selectable,
-          showIcon,
-          icon,
-          switcherIcon,
-          draggable: draggableConfig,
-          draggingNodeKey,
-          checkable,
-          checkStrictly,
-          disabled,
-          keyEntities,
-          dropLevelOffset,
-          dropContainerKey,
-          dropTargetKey,
-          dropPosition,
-          dragOverNodeKey,
-          indent,
-          direction,
-          dropIndicatorRender,
-
-          loadData,
-          filterTreeNode,
-
-          titleRender,
-
-          onNodeClick: this.onNodeClick,
-          onNodeDoubleClick: this.onNodeDoubleClick,
-          onNodeExpand: this.onNodeExpand,
-          onNodeSelect: this.onNodeSelect,
-          onNodeCheck: this.onNodeCheck,
-          onNodeLoad: this.onNodeLoad,
-          onNodeMouseEnter: this.onNodeMouseEnter,
-          onNodeMouseLeave: this.onNodeMouseLeave,
-          onNodeContextMenu: this.onNodeContextMenu,
-          onNodeDragStart: this.onNodeDragStart,
-          onNodeDragEnter: this.onNodeDragEnter,
-          onNodeDragOver: this.onNodeDragOver,
-          onNodeDragLeave: this.onNodeDragLeave,
-          onNodeDragEnd: this.onNodeDragEnd,
-          onNodeDrop: this.onNodeDrop,
-        }}
-      >
+      <TreeContext.Provider value={contextValue}>
         <div
           className={classNames(prefixCls, className, rootClassName, {
             [`${prefixCls}-show-line`]: showLine,
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 6f99ce38..8bc924c7 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -60,8 +60,8 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
   dropIndicatorRender: (props: {
     dropPosition: -1 | 0 | 1;
     dropLevelOffset: number;
-    indent;
-    prefixCls;
+    indent: number;
+    prefixCls: string;
     direction: Direction;
   }) => React.ReactNode;
   dragOverNodeKey: Key | null;
@@ -92,9 +92,7 @@ export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode>
   onNodeDrop: NodeDragEventHandler<any, any>;
 }
 
-export const TreeContext: React.Context<TreeContextProps<any> | null> = React.createContext(null);
+export const TreeContext = React.createContext<TreeContextProps<any>>(null);
 
 /** Internal usage, safe to remove. Do not use in prod */
-export const UnstableContext = React.createContext<{
-  nodeDisabled?: (node: DataNode) => boolean;
-}>({});
+export const UnstableContext = React.createContext<{ nodeDisabled?: (n: DataNode) => boolean }>({});
diff --git a/src/util.tsx b/src/util.tsx
index c689d028..f9135acc 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -309,7 +309,7 @@ export function parseCheckedKeys(keys: Key[] | { checked: Key[]; halfChecked: Ke
   }
 
   // Convert keys to object format
-  let keyProps;
+  let keyProps: { checkedKeys?: Key[]; halfCheckedKeys?: Key[] };
   if (Array.isArray(keys)) {
     // [Legacy] Follow the api doc
     keyProps = {

From 9e037b32885daefc4950eca4f839834ce0c250ed Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 2 Jan 2025 05:35:51 +0800
Subject: [PATCH 373/397] chore(deps-dev): bump rc-tooltip from 5.3.1 to 6.3.2
 (#902)

* chore(deps-dev): bump rc-tooltip from 5.3.1 to 6.3.2

Bumps [rc-tooltip](https://github.com/react-component/tooltip) from 5.3.1 to 6.3.2.
- [Release notes](https://github.com/react-component/tooltip/releases)
- [Changelog](https://github.com/react-component/tooltip/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-component/tooltip/compare/v5.3.1...v6.3.2)

---
updated-dependencies:
- dependency-name: rc-tooltip
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update package.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: lijianan <574980606@qq.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 3c12f56a..d1f897be 100644
--- a/package.json
+++ b/package.json
@@ -73,7 +73,7 @@
     "prettier": "^3.3.3",
     "rc-dialog": "^8.1.0",
     "rc-test": "^7.0.15",
-    "rc-tooltip": "5.x",
+    "rc-tooltip": "^6.3.2",
     "rc-trigger": "^5.0.7",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",

From fdb35f9d8b4c0d75a56312e96af936192526fdf2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 2 Jan 2025 05:42:39 +0800
Subject: [PATCH 374/397] chore(deps-dev): bump rc-dialog from 8.9.0 to 9.6.0
 (#862)

Bumps [rc-dialog](https://github.com/react-component/dialog) from 8.9.0 to 9.6.0.
- [Release notes](https://github.com/react-component/dialog/releases)
- [Changelog](https://github.com/react-component/dialog/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-component/dialog/compare/v8.9.0...v9.6.0)

---
updated-dependencies:
- dependency-name: rc-dialog
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: lijianan <574980606@qq.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d1f897be..9a54bd05 100644
--- a/package.json
+++ b/package.json
@@ -71,7 +71,7 @@
     "lint-staged": "^15.2.10",
     "np": "^10.0.5",
     "prettier": "^3.3.3",
-    "rc-dialog": "^8.1.0",
+    "rc-dialog": "^9.0.0",
     "rc-test": "^7.0.15",
     "rc-tooltip": "^6.3.2",
     "rc-trigger": "^5.0.7",

From 9912c1fac0305515fa1fb4c9ec5210dc916e93c3 Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Thu, 2 Jan 2025 16:32:58 +0800
Subject: [PATCH 375/397] refactor: rewrite TreeNode CC => FC (#905)

* chore: code optimization

* refactor: rewrite TreeNode CC => FC

* fix: fix

* fix: fix

* fix: fix

* fix: fix

* fix: fix
---
 src/Tree.tsx        |  89 ++---
 src/TreeNode.tsx    | 779 ++++++++++++++++++--------------------------
 src/contextTypes.ts |   4 +-
 src/util.tsx        |  12 +-
 4 files changed, 370 insertions(+), 514 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index d5d99e28..44641eca 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -26,9 +26,9 @@ import type {
   IconType,
   Key,
   KeyEntities,
-  NodeInstance,
   SafeKey,
   ScrollTo,
+  TreeNodeProps,
 } from './interface';
 import NodeList, { MOTION_KEY, MotionEntity, type NodeListRef } from './NodeList';
 import TreeNode from './TreeNode';
@@ -306,7 +306,7 @@ class Tree<
 
   dragStartMousePosition = null;
 
-  dragNode: NodeInstance<TreeDataType>;
+  dragNodeProps: TreeNodeProps<TreeDataType> = null;
 
   currentMouseOverDroppableNodeKey = null;
 
@@ -472,12 +472,12 @@ class Tree<
     return newState;
   }
 
-  onNodeDragStart: NodeDragEventHandler<TreeDataType, HTMLDivElement> = (event, node) => {
+  onNodeDragStart: NodeDragEventHandler<TreeDataType, HTMLDivElement> = (event, nodeProps) => {
     const { expandedKeys, keyEntities } = this.state;
     const { onDragStart } = this.props;
-    const { eventKey } = node.props;
+    const { eventKey } = nodeProps;
 
-    this.dragNode = node;
+    this.dragNodeProps = nodeProps;
     this.dragStartMousePosition = {
       x: event.clientX,
       y: event.clientY,
@@ -495,7 +495,7 @@ class Tree<
 
     window.addEventListener('dragend', this.onWindowDragEnd);
 
-    onDragStart?.({ event, node: convertNodePropsToEventData<TreeDataType>(node.props) });
+    onDragStart?.({ event, node: convertNodePropsToEventData<TreeDataType>(nodeProps) });
   };
 
   /**
@@ -505,18 +505,20 @@ class Tree<
    * Better for use mouse move event to refresh drag state.
    * But let's just keep it to avoid event trigger logic change.
    */
-  onNodeDragEnter = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance<TreeDataType>) => {
+  onNodeDragEnter = (
+    event: React.DragEvent<HTMLDivElement>,
+    nodeProps: TreeNodeProps<TreeDataType>,
+  ) => {
     const { expandedKeys, keyEntities, dragChildrenKeys, flattenNodes, indent } = this.state;
     const { onDragEnter, onExpand, allowDrop, direction } = this.props;
-    const { pos, eventKey } = node.props;
-    const { dragNode } = this;
+    const { pos, eventKey } = nodeProps;
 
     // record the key of node which is latest entered, used in dragleave event.
     if (this.currentMouseOverDroppableNodeKey !== eventKey) {
       this.currentMouseOverDroppableNodeKey = eventKey;
     }
 
-    if (!dragNode) {
+    if (!this.dragNodeProps) {
       this.resetDragState();
       return;
     }
@@ -531,8 +533,8 @@ class Tree<
       dragOverNodeKey,
     } = calcDropPosition<TreeDataType>(
       event,
-      dragNode,
-      node,
+      this.dragNodeProps,
+      nodeProps,
       indent,
       this.dragStartMousePosition,
       allowDrop,
@@ -560,20 +562,22 @@ class Tree<
       clearTimeout(this.delayedDragEnterLogic[key]);
     });
 
-    if (dragNode.props.eventKey !== node.props.eventKey) {
+    if (this.dragNodeProps.eventKey !== nodeProps.eventKey) {
       // hoist expand logic here
       // since if logic is on the bottom
       // it will be blocked by abstract dragover node check
       //   => if you dragenter from top, you mouse will still be consider as in the top node
       event.persist();
       this.delayedDragEnterLogic[pos] = window.setTimeout(() => {
-        if (this.state.draggingNodeKey === null) return;
+        if (this.state.draggingNodeKey === null) {
+          return;
+        }
 
         let newExpandedKeys = [...expandedKeys];
-        const entity = getEntity(keyEntities, node.props.eventKey);
+        const entity = getEntity(keyEntities, nodeProps.eventKey);
 
         if (entity && (entity.children || []).length) {
-          newExpandedKeys = arrAdd(expandedKeys, node.props.eventKey);
+          newExpandedKeys = arrAdd(expandedKeys, nodeProps.eventKey);
         }
 
         if (!this.props.hasOwnProperty('expandedKeys')) {
@@ -581,7 +585,7 @@ class Tree<
         }
 
         onExpand?.(newExpandedKeys, {
-          node: convertNodePropsToEventData<TreeDataType>(node.props),
+          node: convertNodePropsToEventData<TreeDataType>(nodeProps),
           expanded: true,
           nativeEvent: event.nativeEvent,
         });
@@ -589,7 +593,7 @@ class Tree<
     }
 
     // Skip if drag node is self
-    if (dragNode.props.eventKey === dropTargetKey && dropLevelOffset === 0) {
+    if (this.dragNodeProps.eventKey === dropTargetKey && dropLevelOffset === 0) {
       this.resetDragState();
       return;
     }
@@ -607,17 +611,19 @@ class Tree<
 
     onDragEnter?.({
       event,
-      node: convertNodePropsToEventData<TreeDataType>(node.props),
+      node: convertNodePropsToEventData<TreeDataType>(nodeProps),
       expandedKeys,
     });
   };
 
-  onNodeDragOver = (event: React.DragEvent<HTMLDivElement>, node: NodeInstance<TreeDataType>) => {
+  onNodeDragOver = (
+    event: React.DragEvent<HTMLDivElement>,
+    nodeProps: TreeNodeProps<TreeDataType>,
+  ) => {
     const { dragChildrenKeys, flattenNodes, keyEntities, expandedKeys, indent } = this.state;
     const { onDragOver, allowDrop, direction } = this.props;
-    const { dragNode } = this;
 
-    if (!dragNode) {
+    if (!this.dragNodeProps) {
       return;
     }
 
@@ -631,8 +637,8 @@ class Tree<
       dragOverNodeKey,
     } = calcDropPosition<TreeDataType>(
       event,
-      dragNode,
-      node,
+      this.dragNodeProps,
+      nodeProps,
       indent,
       this.dragStartMousePosition,
       allowDrop,
@@ -650,7 +656,7 @@ class Tree<
 
     // Update drag position
 
-    if (dragNode.props.eventKey === dropTargetKey && dropLevelOffset === 0) {
+    if (this.dragNodeProps.eventKey === dropTargetKey && dropLevelOffset === 0) {
       if (
         !(
           this.state.dropPosition === null &&
@@ -686,14 +692,14 @@ class Tree<
       });
     }
 
-    onDragOver?.({ event, node: convertNodePropsToEventData<TreeDataType>(node.props) });
+    onDragOver?.({ event, node: convertNodePropsToEventData<TreeDataType>(nodeProps) });
   };
 
-  onNodeDragLeave: NodeDragEventHandler<TreeDataType> = (event, node) => {
+  onNodeDragLeave: NodeDragEventHandler<TreeDataType> = (event, nodeProps) => {
     // if it is outside the droppable area
     // currentMouseOverDroppableNodeKey will be updated in dragenter event when into another droppable receiver.
     if (
-      this.currentMouseOverDroppableNodeKey === node.props.eventKey &&
+      this.currentMouseOverDroppableNodeKey === nodeProps.eventKey &&
       !event.currentTarget.contains(event.relatedTarget as Node)
     ) {
       this.resetDragState();
@@ -702,7 +708,7 @@ class Tree<
 
     const { onDragLeave } = this.props;
 
-    onDragLeave?.({ event, node: convertNodePropsToEventData<TreeDataType>(node.props) });
+    onDragLeave?.({ event, node: convertNodePropsToEventData<TreeDataType>(nodeProps) });
   };
 
   // since stopPropagation() is called in treeNode
@@ -713,7 +719,7 @@ class Tree<
   };
 
   // if onNodeDragEnd is called, onWindowDragEnd won't be called since stopPropagation() is called
-  onNodeDragEnd: NodeDragEventHandler<TreeDataType> = (event, node) => {
+  onNodeDragEnd: NodeDragEventHandler<TreeDataType> = (event, nodeProps) => {
     const { onDragEnd } = this.props;
     this.setState({
       dragOverNodeKey: null,
@@ -721,22 +727,24 @@ class Tree<
 
     this.cleanDragState();
 
-    onDragEnd?.({ event, node: convertNodePropsToEventData<TreeDataType>(node.props) });
+    onDragEnd?.({ event, node: convertNodePropsToEventData<TreeDataType>(nodeProps) });
 
-    this.dragNode = null;
+    this.dragNodeProps = null;
 
     window.removeEventListener('dragend', this.onWindowDragEnd);
   };
 
   onNodeDrop = (
     event: React.DragEvent<HTMLDivElement>,
-    _: NodeInstance<TreeDataType>,
+    _: TreeNodeProps<TreeDataType>,
     outsideTree: boolean = false,
   ) => {
     const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } =
       this.state;
 
-    if (!dropAllowed) return;
+    if (!dropAllowed) {
+      return;
+    }
 
     const { onDrop } = this.props;
 
@@ -752,6 +760,7 @@ class Tree<
       active: this.getActiveItem()?.key === dropTargetKey,
       data: getEntity(this.state.keyEntities, dropTargetKey).node,
     };
+
     const dropToChild = dragChildrenKeys.includes(dropTargetKey);
 
     warning(
@@ -764,8 +773,8 @@ class Tree<
     const dropResult = {
       event,
       node: convertNodePropsToEventData<TreeDataType>(abstractDropNodeProps),
-      dragNode: this.dragNode ? convertNodePropsToEventData(this.dragNode.props) : null,
-      dragNodesKeys: [this.dragNode.props.eventKey].concat(dragChildrenKeys),
+      dragNode: this.dragNodeProps ? convertNodePropsToEventData(this.dragNodeProps) : null,
+      dragNodesKeys: [this.dragNodeProps.eventKey].concat(dragChildrenKeys),
       dropToGap: dropPosition !== 0,
       dropPosition: dropPosition + Number(posArr[posArr.length - 1]),
     };
@@ -774,7 +783,7 @@ class Tree<
       onDrop?.(dropResult);
     }
 
-    this.dragNode = null;
+    this.dragNodeProps = null;
   };
 
   resetDragState() {
@@ -865,9 +874,7 @@ class Tree<
     const selectedNodes = selectedKeys
       .map(selectedKey => {
         const entity = getEntity(keyEntities, selectedKey);
-        if (!entity) return null;
-
-        return entity.node;
+        return entity ? entity.node : null;
       })
       .filter(Boolean);
 
@@ -970,7 +977,7 @@ class Tree<
 
     const loadPromise = new Promise<void>((resolve, reject) => {
       // We need to get the latest state of loading/loaded keys
-      this.setState(({ loadedKeys = [], loadingKeys = [] }): any => {
+      this.setState(({ loadedKeys = [], loadingKeys = [] }) => {
         const { loadData, onLoad } = this.props;
 
         if (!loadData || loadedKeys.includes(key) || loadingKeys.includes(key)) {
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index ee388798..22d2f1d3 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -1,8 +1,7 @@
 import classNames from 'classnames';
 import pickAttrs from 'rc-util/lib/pickAttrs';
-import * as React from 'react';
-// @ts-ignore
-import { TreeContext, UnstableContext, type TreeContextProps } from './contextTypes';
+import React from 'react';
+import { TreeContext, UnstableContext } from './contextTypes';
 import Indent from './Indent';
 import type { TreeNodeProps } from './interface';
 import getEntity from './utils/keyUtil';
@@ -15,603 +14,453 @@ const defaultTitle = '---';
 
 export type { TreeNodeProps } from './interface';
 
-export interface InternalTreeNodeProps extends TreeNodeProps {
-  context?: TreeContextProps;
-  unstableContext?: React.ContextType<typeof UnstableContext>;
-}
-
-export interface TreeNodeState {
-  dragNodeHighlight: boolean;
-}
-
-class InternalTreeNode extends React.Component<InternalTreeNodeProps, TreeNodeState> {
-  public state = {
-    dragNodeHighlight: false,
-  };
-
-  public selectHandle: HTMLSpanElement;
-
-  cacheIndent: number;
-
-  // Isomorphic needn't load data in server side
-  componentDidMount() {
-    this.syncLoadData(this.props);
-  }
+const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
+  const {
+    eventKey,
+    className,
+    style,
+    dragOver,
+    dragOverGapTop,
+    dragOverGapBottom,
+    isLeaf,
+    isStart,
+    isEnd,
+    expanded,
+    selected,
+    checked,
+    halfChecked,
+    loading,
+    domRef,
+    active,
+    data,
+    onMouseMove,
+    selectable,
+    ...otherProps
+  } = props;
+
+  const context = React.useContext(TreeContext);
+
+  const unstableContext = React.useContext(UnstableContext);
+
+  const selectHandleRef = React.useRef<HTMLSpanElement>(null);
+
+  const [dragNodeHighlight, setDragNodeHighlight] = React.useState<boolean>(false);
+
+  // ======= State: Selectable Check =======
+  const memoizedIsSelectable = React.useMemo<boolean>(() => {
+    // Ignore when selectable is undefined or null
+    if (typeof selectable === 'boolean') {
+      return selectable;
+    }
+    return context.selectable;
+  }, [selectable, context.selectable]);
 
-  componentDidUpdate() {
-    this.syncLoadData(this.props);
-  }
+  // ======= State: Disabled State =======
+  const isDisabled = React.useMemo<boolean>(() => {
+    return !!(context.disabled || props.disabled || unstableContext.nodeDisabled?.(data));
+  }, [context.disabled, props.disabled, unstableContext.nodeDisabled, data]);
 
-  onSelectorClick = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
-    // Click trigger before select/check operation
-    const {
-      context: { onNodeClick },
-    } = this.props;
-    onNodeClick(e, convertNodePropsToEventData(this.props));
+  const isCheckable = React.useMemo<React.ReactNode>(() => {
+    // Return false if tree or treeNode is not checkable
+    if (!context.checkable || props.checkable === false) {
+      return false;
+    }
+    return context.checkable;
+  }, [context.checkable, props.checkable]);
 
-    if (this.isSelectable()) {
-      this.onSelect(e);
-    } else {
-      this.onCheck(e);
+  // ======= Event Handlers: Selection and Check =======
+  const onSelect = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
+    if (isDisabled) {
+      return;
     }
+    context.onNodeSelect(e, convertNodePropsToEventData(props));
   };
 
-  onSelectorDoubleClick = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
-    const {
-      context: { onNodeDoubleClick },
-    } = this.props;
-    onNodeDoubleClick(e, convertNodePropsToEventData(this.props));
+  const onCheck = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
+    if (isDisabled) {
+      return;
+    }
+    if (!isCheckable || props.disableCheckbox) {
+      return;
+    }
+    context.onNodeCheck(e, convertNodePropsToEventData(props), !checked);
   };
 
-  onSelect = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
-    if (this.isDisabled()) return;
-
-    const {
-      context: { onNodeSelect },
-    } = this.props;
-    onNodeSelect(e, convertNodePropsToEventData(this.props));
+  const onSelectorClick = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
+    // Click trigger before select/check operation
+    context.onNodeClick(e, convertNodePropsToEventData(props));
+    if (memoizedIsSelectable) {
+      onSelect(e);
+    } else {
+      onCheck(e);
+    }
   };
 
-  onCheck = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
-    if (this.isDisabled()) return;
-
-    const { disableCheckbox, checked } = this.props;
-    const {
-      context: { onNodeCheck },
-    } = this.props;
-
-    if (!this.isCheckable() || disableCheckbox) return;
-
-    const targetChecked = !checked;
-    onNodeCheck(e, convertNodePropsToEventData(this.props), targetChecked);
+  const onSelectorDoubleClick = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
+    context.onNodeDoubleClick(e, convertNodePropsToEventData(props));
   };
 
-  onMouseEnter = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
-    const {
-      context: { onNodeMouseEnter },
-    } = this.props;
-    onNodeMouseEnter(e, convertNodePropsToEventData(this.props));
+  const onMouseEnter = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
+    context.onNodeMouseEnter(e, convertNodePropsToEventData(props));
   };
 
-  onMouseLeave = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
-    const {
-      context: { onNodeMouseLeave },
-    } = this.props;
-    onNodeMouseLeave(e, convertNodePropsToEventData(this.props));
+  const onMouseLeave = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
+    context.onNodeMouseLeave(e, convertNodePropsToEventData(props));
   };
 
-  onContextMenu = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
-    const {
-      context: { onNodeContextMenu },
-    } = this.props;
-    onNodeContextMenu(e, convertNodePropsToEventData(this.props));
+  const onContextMenu = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
+    context.onNodeContextMenu(e, convertNodePropsToEventData(props));
   };
 
-  onDragStart = (e: React.DragEvent<HTMLDivElement>) => {
-    const {
-      context: { onNodeDragStart },
-    } = this.props;
-
+  // ======= Drag: Drag Event Handlers =======
+  const onDragStart = (e: React.DragEvent<HTMLDivElement>) => {
     e.stopPropagation();
-    this.setState({
-      dragNodeHighlight: true,
-    });
-    onNodeDragStart(e, this);
-
+    setDragNodeHighlight(true);
+    context.onNodeDragStart(e, props);
     try {
       // ie throw error
       // firefox-need-it
       e.dataTransfer.setData('text/plain', '');
-    } catch (error) {
+    } catch {
       // empty
     }
   };
 
-  onDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
-    const {
-      context: { onNodeDragEnter },
-    } = this.props;
-
+  const onDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
     e.preventDefault();
     e.stopPropagation();
-    onNodeDragEnter(e, this);
+    context.onNodeDragEnter(e, props);
   };
 
-  onDragOver = (e: React.DragEvent<HTMLDivElement>) => {
-    const {
-      context: { onNodeDragOver },
-    } = this.props;
-
+  const onDragOver = (e: React.DragEvent<HTMLDivElement>) => {
     e.preventDefault();
     e.stopPropagation();
-    onNodeDragOver(e, this);
+    context.onNodeDragOver(e, props);
   };
 
-  onDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
-    const {
-      context: { onNodeDragLeave },
-    } = this.props;
-
+  const onDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
     e.stopPropagation();
-    onNodeDragLeave(e, this);
+    context.onNodeDragLeave(e, props);
   };
 
-  onDragEnd = (e: React.DragEvent<HTMLDivElement>) => {
-    const {
-      context: { onNodeDragEnd },
-    } = this.props;
-
+  const onDragEnd = (e: React.DragEvent<HTMLDivElement>) => {
     e.stopPropagation();
-    this.setState({
-      dragNodeHighlight: false,
-    });
-    onNodeDragEnd(e, this);
+    setDragNodeHighlight(false);
+    context.onNodeDragEnd(e, props);
   };
 
-  onDrop = (e: React.DragEvent<HTMLDivElement>) => {
-    const {
-      context: { onNodeDrop },
-    } = this.props;
-
+  const onDrop = (e: React.DragEvent<HTMLDivElement>) => {
     e.preventDefault();
     e.stopPropagation();
-    this.setState({
-      dragNodeHighlight: false,
-    });
-    onNodeDrop(e, this);
-  };
-
-  // Disabled item still can be switch
-  onExpand: React.MouseEventHandler<HTMLDivElement> = e => {
-    const {
-      loading,
-      context: { onNodeExpand },
-    } = this.props;
-    if (loading) return;
-    onNodeExpand(e, convertNodePropsToEventData(this.props));
-  };
-
-  // Drag usage
-  setSelectHandle = node => {
-    this.selectHandle = node;
+    setDragNodeHighlight(false);
+    context.onNodeDrop(e, props);
   };
 
-  getNodeState = () => {
-    const { expanded } = this.props;
-
-    if (this.isLeaf()) {
-      return null;
+  // ======= Expand: Node Expansion =======
+  const onExpand: React.MouseEventHandler<HTMLDivElement> = e => {
+    if (loading) {
+      return;
     }
-
-    return expanded ? ICON_OPEN : ICON_CLOSE;
-  };
-
-  hasChildren = () => {
-    const { eventKey } = this.props;
-    const {
-      context: { keyEntities },
-    } = this.props;
-    const { children } = getEntity(keyEntities, eventKey) || {};
-
-    return !!(children || []).length;
+    context.onNodeExpand(e, convertNodePropsToEventData(props));
   };
 
-  isLeaf = () => {
-    const { isLeaf, loaded } = this.props;
-    const {
-      context: { loadData },
-    } = this.props;
-
-    const hasChildren = this.hasChildren();
+  // ======= State: Has Children =======
+  const hasChildren = React.useMemo<boolean>(() => {
+    const { children } = getEntity(context.keyEntities, eventKey) || {};
+    return Boolean((children || []).length);
+  }, [context.keyEntities, eventKey]);
 
+  // ======= State: Leaf Check =======
+  const memoizedIsLeaf = React.useMemo<boolean>(() => {
     if (isLeaf === false) {
       return false;
     }
+    return (
+      isLeaf ||
+      (!context.loadData && !hasChildren) ||
+      (context.loadData && props.loaded && !hasChildren)
+    );
+  }, [isLeaf, context.loadData, hasChildren, props.loaded]);
 
-    return isLeaf || (!loadData && !hasChildren) || (loadData && loaded && !hasChildren);
-  };
-
-  isDisabled = () => {
-    const { disabled, data } = this.props;
-    const {
-      context: { disabled: treeDisabled },
-      unstableContext: { nodeDisabled },
-    } = this.props;
-
-    return !!(treeDisabled || disabled || (nodeDisabled && nodeDisabled(data)));
-  };
-
-  isCheckable = () => {
-    const { checkable } = this.props;
-    const {
-      context: { checkable: treeCheckable },
-    } = this.props;
-
-    // Return false if tree or treeNode is not checkable
-    if (!treeCheckable || checkable === false) return false;
-    return treeCheckable;
-  };
+  // ============== State: Node State (Open/Close) ==============
+  const nodeState = React.useMemo<typeof ICON_OPEN | typeof ICON_CLOSE>(() => {
+    if (memoizedIsLeaf) {
+      return null;
+    }
+    return expanded ? ICON_OPEN : ICON_CLOSE;
+  }, [memoizedIsLeaf, expanded]);
 
   // Load data to avoid default expanded tree without data
-  syncLoadData = (props: InternalTreeNodeProps) => {
-    const { expanded, loading, loaded } = props;
-    const {
-      context: { loadData, onNodeLoad },
-    } = this.props;
-
+  // ============== Effect: Sync Load Data ==============
+  const syncLoadData = () => {
     if (loading) {
       return;
     }
-
     // read from state to avoid loadData at same time
-    if (loadData && expanded && !this.isLeaf() && !loaded) {
+    if (context.loadData && expanded && !memoizedIsLeaf && !props.loaded) {
       // We needn't reload data when has children in sync logic
       // It's only needed in node expanded
-      onNodeLoad(convertNodePropsToEventData(this.props));
+      context.onNodeLoad(convertNodePropsToEventData(props));
     }
   };
 
-  isSelectable() {
-    const { selectable } = this.props;
-    const {
-      context: { selectable: treeSelectable },
-    } = this.props;
-
-    // Ignore when selectable is undefined or null
-    if (typeof selectable === 'boolean') {
-      return selectable;
-    }
-
-    return treeSelectable;
-  }
-
-  isDraggable = () => {
-    const {
-      data,
-      context: { draggable },
-    } = this.props;
+  React.useEffect(() => {
+    syncLoadData();
+  }, [loading, context.loadData, context.onNodeLoad, expanded, memoizedIsLeaf, props]);
 
-    return !!(draggable && (!draggable.nodeDraggable || draggable.nodeDraggable(data)));
-  };
+  const isDraggable = React.useMemo<boolean>(() => {
+    return !!(
+      context.draggable &&
+      (!context.draggable.nodeDraggable || context.draggable.nodeDraggable(data))
+    );
+  }, [context.draggable, data]);
 
   // ==================== Render: Drag Handler ====================
-  renderDragHandler = () => {
-    const {
-      context: { draggable, prefixCls },
-    } = this.props;
-
-    return draggable?.icon ? (
-      <span className={`${prefixCls}-draggable-icon`}>{draggable.icon}</span>
-    ) : null;
-  };
+  const dragHandlerNode = React.useMemo<React.ReactNode>(() => {
+    if (!context.draggable?.icon) {
+      return null;
+    }
+    return <span className={`${context.prefixCls}-draggable-icon`}>{context.draggable.icon}</span>;
+  }, [context.draggable]);
 
   // ====================== Render: Switcher ======================
-  renderSwitcherIconDom = (isLeaf: boolean) => {
-    const { switcherIcon: switcherIconFromProps } = this.props;
-    const {
-      context: { switcherIcon: switcherIconFromCtx },
-    } = this.props;
-
-    const switcherIcon = switcherIconFromProps || switcherIconFromCtx;
+  const renderSwitcherIconDom = (isInternalLeaf: boolean) => {
+    const switcherIcon = props.switcherIcon || context.switcherIcon;
     // if switcherIconDom is null, no render switcher span
     if (typeof switcherIcon === 'function') {
-      return switcherIcon({ ...this.props, isLeaf });
+      return switcherIcon({ ...props, isLeaf: isInternalLeaf });
     }
     return switcherIcon;
   };
 
   // Switcher
-  renderSwitcher = () => {
-    const { expanded } = this.props;
-    const {
-      context: { prefixCls },
-    } = this.props;
-
-    if (this.isLeaf()) {
+  const switcherNode = React.useMemo<React.ReactNode>(() => {
+    if (memoizedIsLeaf) {
       // if switcherIconDom is null, no render switcher span
-      const switcherIconDom = this.renderSwitcherIconDom(true);
-
+      const switcherIconDom = renderSwitcherIconDom(true);
       return switcherIconDom !== false ? (
-        <span className={classNames(`${prefixCls}-switcher`, `${prefixCls}-switcher-noop`)}>
+        <span
+          className={classNames(
+            `${context.prefixCls}-switcher`,
+            `${context.prefixCls}-switcher-noop`,
+          )}
+        >
           {switcherIconDom}
         </span>
       ) : null;
     }
-
-    const switcherCls = classNames(
-      `${prefixCls}-switcher`,
-      `${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`,
-    );
-
-    const switcherIconDom = this.renderSwitcherIconDom(false);
-
+    const switcherIconDom = renderSwitcherIconDom(false);
     return switcherIconDom !== false ? (
-      <span onClick={this.onExpand} className={switcherCls}>
+      <span
+        onClick={onExpand}
+        className={classNames(
+          `${context.prefixCls}-switcher`,
+          `${context.prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`,
+        )}
+      >
         {switcherIconDom}
       </span>
     ) : null;
-  };
-
-  // ====================== Render: Checkbox ======================
-  // Checkbox
-  renderCheckbox = () => {
-    const { checked, halfChecked, disableCheckbox, title } = this.props;
-    const {
-      context: { prefixCls },
-    } = this.props;
-    const disabled = this.isDisabled();
-    const checkable = this.isCheckable();
+  }, [context.prefixCls, expanded, memoizedIsLeaf, props.switcherIcon, context.switcherIcon]);
 
-    if (!checkable) return null;
+  // ====================== Checkbox ======================
+  const checkboxNode = React.useMemo<React.ReactNode>(() => {
+    if (!isCheckable) {
+      return null;
+    }
 
     // [Legacy] Custom element should be separate with `checkable` in future
-    const $custom = typeof checkable !== 'boolean' ? checkable : null;
+    const $custom = typeof isCheckable !== 'boolean' ? isCheckable : null;
 
     return (
       <span
-        className={classNames(
-          `${prefixCls}-checkbox`,
-          checked && `${prefixCls}-checkbox-checked`,
-          !checked && halfChecked && `${prefixCls}-checkbox-indeterminate`,
-          (disabled || disableCheckbox) && `${prefixCls}-checkbox-disabled`,
-        )}
-        onClick={this.onCheck}
+        className={classNames(`${context.prefixCls}-checkbox`, {
+          [`${context.prefixCls}-checkbox-checked`]: checked,
+          [`${context.prefixCls}-checkbox-indeterminate`]: !checked && halfChecked,
+          [`${context.prefixCls}-checkbox-disabled`]: isDisabled || props.disableCheckbox,
+        })}
+        onClick={onCheck}
         role="checkbox"
         aria-checked={halfChecked ? 'mixed' : checked}
-        aria-disabled={disabled || disableCheckbox}
-        aria-label={`Select ${typeof title === 'string' ? title : 'tree node'}`}
+        aria-disabled={isDisabled || props.disableCheckbox}
+        aria-label={`Select ${typeof props.title === 'string' ? props.title : 'tree node'}`}
       >
         {$custom}
       </span>
     );
-  };
+  }, [isCheckable, checked, halfChecked, isDisabled, props.disableCheckbox, props.title]);
 
   // ==================== Render: Title + Icon ====================
-  renderIcon = () => {
-    const { loading } = this.props;
-    const {
-      context: { prefixCls },
-    } = this.props;
-
+  const iconNode = React.useMemo<React.ReactNode>(() => {
     return (
       <span
         className={classNames(
-          `${prefixCls}-iconEle`,
-          `${prefixCls}-icon__${this.getNodeState() || 'docu'}`,
-          loading && `${prefixCls}-icon_loading`,
+          `${context.prefixCls}-iconEle`,
+          `${context.prefixCls}-icon__${nodeState || 'docu'}`,
+          { [`${context.prefixCls}-icon_loading`]: loading },
         )}
       />
     );
-  };
+  }, [context.prefixCls, nodeState, loading]);
+
+  // =================== Drop Indicator ===================
+  const dropIndicatorNode = React.useMemo<React.ReactNode>(() => {
+    const rootDraggable = Boolean(context.draggable);
+    // allowDrop is calculated in Tree.tsx, there is no need for calc it here
+    const showIndicator = !props.disabled && rootDraggable && context.dragOverNodeKey === eventKey;
+    if (!showIndicator) {
+      return null;
+    }
+    return context.dropIndicatorRender({
+      dropPosition: context.dropPosition,
+      dropLevelOffset: context.dropLevelOffset,
+      indent: context.indent,
+      prefixCls: context.prefixCls,
+      direction: context.direction,
+    });
+  }, [
+    context.dropPosition,
+    context.dropLevelOffset,
+    context.indent,
+    context.prefixCls,
+    context.direction,
+    context.draggable,
+    context.dragOverNodeKey,
+    context.dropIndicatorRender,
+  ]);
 
   // Icon + Title
-  renderSelector = () => {
-    const { dragNodeHighlight } = this.state;
-    const { title = defaultTitle, selected, icon, loading, data } = this.props;
-    const {
-      context: { prefixCls, showIcon, icon: treeIcon, loadData, titleRender },
-    } = this.props;
-    const disabled = this.isDisabled();
+  const selectorNode = React.useMemo<React.ReactNode>(() => {
+    const { title = defaultTitle } = props;
 
-    const wrapClass = `${prefixCls}-node-content-wrapper`;
+    const wrapClass = `${context.prefixCls}-node-content-wrapper`;
 
     // Icon - Still show loading icon when loading without showIcon
-    let $icon;
+    let $icon: React.ReactNode;
 
-    if (showIcon) {
-      const currentIcon = icon || treeIcon;
+    if (context.showIcon) {
+      const currentIcon = props.icon || context.icon;
 
       $icon = currentIcon ? (
-        <span className={classNames(`${prefixCls}-iconEle`, `${prefixCls}-icon__customize`)}>
-          {typeof currentIcon === 'function' ? currentIcon(this.props) : currentIcon}
+        <span
+          className={classNames(
+            `${context.prefixCls}-iconEle`,
+            `${context.prefixCls}-icon__customize`,
+          )}
+        >
+          {typeof currentIcon === 'function' ? currentIcon(props) : currentIcon}
         </span>
       ) : (
-        this.renderIcon()
+        iconNode
       );
-    } else if (loadData && loading) {
-      $icon = this.renderIcon();
+    } else if (context.loadData && loading) {
+      $icon = iconNode;
     }
 
     // Title
     let titleNode: React.ReactNode;
     if (typeof title === 'function') {
       titleNode = title(data);
-    } else if (titleRender) {
-      titleNode = titleRender(data);
+    } else if (context.titleRender) {
+      titleNode = context.titleRender(data);
     } else {
       titleNode = title;
     }
 
-    const $title = <span className={`${prefixCls}-title`}>{titleNode}</span>;
-
     return (
       <span
-        ref={this.setSelectHandle}
+        ref={selectHandleRef}
         title={typeof title === 'string' ? title : ''}
-        className={classNames(
-          `${wrapClass}`,
-          `${wrapClass}-${this.getNodeState() || 'normal'}`,
-          !disabled && (selected || dragNodeHighlight) && `${prefixCls}-node-selected`,
-        )}
-        onMouseEnter={this.onMouseEnter}
-        onMouseLeave={this.onMouseLeave}
-        onContextMenu={this.onContextMenu}
-        onClick={this.onSelectorClick}
-        onDoubleClick={this.onSelectorDoubleClick}
+        className={classNames(wrapClass, `${wrapClass}-${nodeState || 'normal'}`, {
+          [`${context.prefixCls}-node-selected`]: !isDisabled && (selected || dragNodeHighlight),
+        })}
+        onMouseEnter={onMouseEnter}
+        onMouseLeave={onMouseLeave}
+        onContextMenu={onContextMenu}
+        onClick={onSelectorClick}
+        onDoubleClick={onSelectorDoubleClick}
       >
         {$icon}
-        {$title}
-        {this.renderDropIndicator()}
+        <span className={`${context.prefixCls}-title`}>{titleNode}</span>
+        {dropIndicatorNode}
       </span>
     );
-  };
-
-  // =================== Render: Drop Indicator ===================
-  renderDropIndicator = () => {
-    const { disabled, eventKey } = this.props;
-    const {
-      context: {
-        draggable,
-        dropLevelOffset,
-        dropPosition,
-        prefixCls,
-        indent,
-        dropIndicatorRender,
-        dragOverNodeKey,
-        direction,
-      },
-    } = this.props;
-    const rootDraggable = !!draggable;
-    // allowDrop is calculated in Tree.tsx, there is no need for calc it here
-    const showIndicator = !disabled && rootDraggable && dragOverNodeKey === eventKey;
-
-    // This is a hot fix which is already fixed in
-    // https://github.com/react-component/tree/pull/743/files
-    // But some case need break point so we hack on this
-    // ref https://github.com/ant-design/ant-design/issues/43493
-    const mergedIndent = indent ?? this.cacheIndent;
-    this.cacheIndent = indent;
-
-    return showIndicator
-      ? dropIndicatorRender({
-          dropPosition,
-          dropLevelOffset,
-          indent: mergedIndent,
-          prefixCls,
-          direction,
-        })
-      : null;
-  };
-
-  // =========================== Render ===========================
-  render() {
-    const {
-      eventKey,
-      className,
-      style,
-      dragOver,
-      dragOverGapTop,
-      dragOverGapBottom,
-      isLeaf,
-      isStart,
-      isEnd,
-      expanded,
-      selected,
-      checked,
-      halfChecked,
-      loading,
-      domRef,
-      active,
-      data,
-      onMouseMove,
-      selectable,
-      ...otherProps
-    } = this.props;
-    const {
-      context: {
-        prefixCls,
-        filterTreeNode,
-        keyEntities,
-        dropContainerKey,
-        dropTargetKey,
-        draggingNodeKey,
-      },
-    } = this.props;
-    const disabled = this.isDisabled();
-    const dataOrAriaAttributeProps = pickAttrs(otherProps, { aria: true, data: true });
-    const { level } = getEntity(keyEntities, eventKey) || {};
-    const isEndNode = isEnd[isEnd.length - 1];
-
-    const mergedDraggable = this.isDraggable();
-    const draggableWithoutDisabled = !disabled && mergedDraggable;
-
-    const dragging = draggingNodeKey === eventKey;
-    const ariaSelected = selectable !== undefined ? { 'aria-selected': !!selectable } : undefined;
-
-    return (
-      <div
-        ref={domRef}
-        role="treeitem"
-        aria-expanded={isLeaf ? undefined : expanded}
-        className={classNames(className, `${prefixCls}-treenode`, {
-          [`${prefixCls}-treenode-disabled`]: disabled,
-          [`${prefixCls}-treenode-switcher-${expanded ? 'open' : 'close'}`]: !isLeaf,
-          [`${prefixCls}-treenode-checkbox-checked`]: checked,
-          [`${prefixCls}-treenode-checkbox-indeterminate`]: halfChecked,
-          [`${prefixCls}-treenode-selected`]: selected,
-          [`${prefixCls}-treenode-loading`]: loading,
-          [`${prefixCls}-treenode-active`]: active,
-          [`${prefixCls}-treenode-leaf-last`]: isEndNode,
-          [`${prefixCls}-treenode-draggable`]: mergedDraggable,
-
-          dragging,
-          'drop-target': dropTargetKey === eventKey,
-          'drop-container': dropContainerKey === eventKey,
-          'drag-over': !disabled && dragOver,
-          'drag-over-gap-top': !disabled && dragOverGapTop,
-          'drag-over-gap-bottom': !disabled && dragOverGapBottom,
-          'filter-node': filterTreeNode && filterTreeNode(convertNodePropsToEventData(this.props)),
-        })}
-        style={style}
-        // Draggable config
-        draggable={draggableWithoutDisabled}
-        onDragStart={draggableWithoutDisabled ? this.onDragStart : undefined}
-        // Drop config
-        onDragEnter={mergedDraggable ? this.onDragEnter : undefined}
-        onDragOver={mergedDraggable ? this.onDragOver : undefined}
-        onDragLeave={mergedDraggable ? this.onDragLeave : undefined}
-        onDrop={mergedDraggable ? this.onDrop : undefined}
-        onDragEnd={mergedDraggable ? this.onDragEnd : undefined}
-        onMouseMove={onMouseMove}
-        {...ariaSelected}
-        {...dataOrAriaAttributeProps}
-      >
-        <Indent prefixCls={prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
-        {this.renderDragHandler()}
-        {this.renderSwitcher()}
-        {this.renderCheckbox()}
-        {this.renderSelector()}
-      </div>
-    );
-  }
-}
-
-const ContextTreeNode: React.FC<TreeNodeProps> = props => (
-  <TreeContext.Consumer>
-    {context => (
-      <UnstableContext.Consumer>
-        {unstableContext => (
-          <InternalTreeNode {...props} context={context} unstableContext={unstableContext} />
-        )}
-      </UnstableContext.Consumer>
-    )}
-  </TreeContext.Consumer>
-);
+  }, [
+    context.prefixCls,
+    context.showIcon,
+    props,
+    context.icon,
+    iconNode,
+    context.titleRender,
+    data,
+    nodeState,
+    onMouseEnter,
+    onMouseLeave,
+    onContextMenu,
+    onSelectorClick,
+    onSelectorDoubleClick,
+  ]);
+
+  const dataOrAriaAttributeProps = pickAttrs(otherProps, { aria: true, data: true });
+
+  const { level } = getEntity(context.keyEntities, eventKey) || {};
+
+  const isEndNode = isEnd[isEnd.length - 1];
+
+  const draggableWithoutDisabled = !isDisabled && isDraggable;
+
+  const dragging = context.draggingNodeKey === eventKey;
+  const ariaSelected = selectable !== undefined ? { 'aria-selected': !!selectable } : undefined;
+  return (
+    <div
+      ref={domRef}
+      role="treeitem"
+      aria-expanded={isLeaf ? undefined : expanded}
+      className={classNames(className, `${context.prefixCls}-treenode`, {
+        [`${context.prefixCls}-treenode-disabled`]: isDisabled,
+        [`${context.prefixCls}-treenode-switcher-${expanded ? 'open' : 'close'}`]: !isLeaf,
+        [`${context.prefixCls}-treenode-checkbox-checked`]: checked,
+        [`${context.prefixCls}-treenode-checkbox-indeterminate`]: halfChecked,
+        [`${context.prefixCls}-treenode-selected`]: selected,
+        [`${context.prefixCls}-treenode-loading`]: loading,
+        [`${context.prefixCls}-treenode-active`]: active,
+        [`${context.prefixCls}-treenode-leaf-last`]: isEndNode,
+        [`${context.prefixCls}-treenode-draggable`]: isDraggable,
+        dragging,
+        'drop-target': context.dropTargetKey === eventKey,
+        'drop-container': context.dropContainerKey === eventKey,
+        'drag-over': !isDisabled && dragOver,
+        'drag-over-gap-top': !isDisabled && dragOverGapTop,
+        'drag-over-gap-bottom': !isDisabled && dragOverGapBottom,
+        'filter-node': context.filterTreeNode?.(convertNodePropsToEventData(props)),
+      })}
+      style={style}
+      // Draggable config
+      draggable={draggableWithoutDisabled}
+      onDragStart={draggableWithoutDisabled ? onDragStart : undefined}
+      // Drop config
+      onDragEnter={isDraggable ? onDragEnter : undefined}
+      onDragOver={isDraggable ? onDragOver : undefined}
+      onDragLeave={isDraggable ? onDragLeave : undefined}
+      onDrop={isDraggable ? onDrop : undefined}
+      onDragEnd={isDraggable ? onDragEnd : undefined}
+      onMouseMove={onMouseMove}
+      {...ariaSelected}
+      {...dataOrAriaAttributeProps}
+    >
+      <Indent prefixCls={context.prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
+      {dragHandlerNode}
+      {switcherNode}
+      {checkboxNode}
+      {selectorNode}
+    </div>
+  );
+};
+
+(TreeNode as any).isTreeNode = 1;
 
 if (process.env.NODE_ENV !== 'production') {
-  ContextTreeNode.displayName = 'TreeNode';
+  TreeNode.displayName = 'TreeNode';
 }
 
-(ContextTreeNode as any).isTreeNode = 1;
-
-export default ContextTreeNode;
+export default TreeNode;
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 8bc924c7..0c525b38 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -11,7 +11,7 @@ import type {
   IconType,
   Key,
   KeyEntities,
-  NodeInstance,
+  TreeNodeProps,
 } from './interface';
 import type { DraggableConfig } from './Tree';
 
@@ -37,7 +37,7 @@ export type NodeMouseEventHandler<
 export type NodeDragEventHandler<
   TreeDataType extends BasicDataNode = DataNode,
   T = HTMLDivElement,
-> = (e: React.DragEvent<T>, node: NodeInstance<TreeDataType>, outsideTree?: boolean) => void;
+> = (e: React.DragEvent<T>, nodeProps: TreeNodeProps<TreeDataType>, outsideTree?: boolean) => void;
 
 export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode> {
   prefixCls: string;
diff --git a/src/util.tsx b/src/util.tsx
index f9135acc..53e4af03 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -14,7 +14,7 @@ import type {
   Key,
   KeyEntities,
   NodeElement,
-  NodeInstance,
+  TreeNodeProps,
 } from './interface';
 import type { AllowDrop, TreeProps } from './Tree';
 import TreeNode from './TreeNode';
@@ -85,8 +85,8 @@ export function isFirstChild<TreeDataType extends BasicDataNode = DataNode>(
 // Only used when drag, not affect SSR.
 export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
   event: React.MouseEvent,
-  dragNode: NodeInstance<TreeDataType>,
-  targetNode: NodeInstance<TreeDataType>,
+  dragNodeProps: TreeNodeProps<TreeDataType>,
+  targetNodeProps: TreeNodeProps<TreeDataType>,
   indent: number,
   startMousePosition: {
     x: number;
@@ -119,7 +119,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
   // find abstract drop node by horizontal offset
   let abstractDropNodeEntity: DataEntity<TreeDataType> = getEntity(
     keyEntities,
-    targetNode.props.eventKey,
+    targetNodeProps.eventKey,
   );
 
   if (clientY < top + height / 2) {
@@ -152,7 +152,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
     }
   }
 
-  const abstractDragDataNode = dragNode.props.data;
+  const abstractDragDataNode = dragNodeProps.data;
   const abstractDropDataNode = abstractDropNodeEntity.node;
   let dropAllowed = true;
   if (
@@ -164,7 +164,7 @@ export function calcDropPosition<TreeDataType extends BasicDataNode = DataNode>(
       dropNode: abstractDropDataNode,
       dropPosition: -1,
     }) &&
-    abstractDropNodeEntity.key === targetNode.props.eventKey
+    abstractDropNodeEntity.key === targetNodeProps.eventKey
   ) {
     // first half of first node in first level
     dropPosition = -1;

From 7440baa08468884f5f54b3d3d7141e07efb96765 Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Thu, 2 Jan 2025 17:26:31 +0800
Subject: [PATCH 376/397] chore: adjust code order (#907)

---
 src/TreeNode.tsx | 52 +++++++++++++++++++++++++-----------------------
 1 file changed, 27 insertions(+), 25 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 22d2f1d3..ae2a4e94 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -46,15 +46,6 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
 
   const [dragNodeHighlight, setDragNodeHighlight] = React.useState<boolean>(false);
 
-  // ======= State: Selectable Check =======
-  const memoizedIsSelectable = React.useMemo<boolean>(() => {
-    // Ignore when selectable is undefined or null
-    if (typeof selectable === 'boolean') {
-      return selectable;
-    }
-    return context.selectable;
-  }, [selectable, context.selectable]);
-
   // ======= State: Disabled State =======
   const isDisabled = React.useMemo<boolean>(() => {
     return !!(context.disabled || props.disabled || unstableContext.nodeDisabled?.(data));
@@ -86,10 +77,19 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
     context.onNodeCheck(e, convertNodePropsToEventData(props), !checked);
   };
 
+  // ======= State: Selectable Check =======
+  const isSelectable = React.useMemo<boolean>(() => {
+    // Ignore when selectable is undefined or null
+    if (typeof selectable === 'boolean') {
+      return selectable;
+    }
+    return context.selectable;
+  }, [selectable, context.selectable]);
+
   const onSelectorClick = (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
     // Click trigger before select/check operation
     context.onNodeClick(e, convertNodePropsToEventData(props));
-    if (memoizedIsSelectable) {
+    if (isSelectable) {
       onSelect(e);
     } else {
       onCheck(e);
@@ -112,6 +112,14 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
     context.onNodeContextMenu(e, convertNodePropsToEventData(props));
   };
 
+  // ======= Drag: Drag Enabled =======
+  const isDraggable = React.useMemo<boolean>(() => {
+    return !!(
+      context.draggable &&
+      (!context.draggable.nodeDraggable || context.draggable.nodeDraggable(data))
+    );
+  }, [context.draggable, data]);
+
   // ======= Drag: Drag Event Handlers =======
   const onDragStart = (e: React.DragEvent<HTMLDivElement>) => {
     e.stopPropagation();
@@ -182,14 +190,6 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
     );
   }, [isLeaf, context.loadData, hasChildren, props.loaded]);
 
-  // ============== State: Node State (Open/Close) ==============
-  const nodeState = React.useMemo<typeof ICON_OPEN | typeof ICON_CLOSE>(() => {
-    if (memoizedIsLeaf) {
-      return null;
-    }
-    return expanded ? ICON_OPEN : ICON_CLOSE;
-  }, [memoizedIsLeaf, expanded]);
-
   // Load data to avoid default expanded tree without data
   // ============== Effect: Sync Load Data ==============
   const syncLoadData = () => {
@@ -204,17 +204,11 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
     }
   };
 
+  // ============== Effect ==============
   React.useEffect(() => {
     syncLoadData();
   }, [loading, context.loadData, context.onNodeLoad, expanded, memoizedIsLeaf, props]);
 
-  const isDraggable = React.useMemo<boolean>(() => {
-    return !!(
-      context.draggable &&
-      (!context.draggable.nodeDraggable || context.draggable.nodeDraggable(data))
-    );
-  }, [context.draggable, data]);
-
   // ==================== Render: Drag Handler ====================
   const dragHandlerNode = React.useMemo<React.ReactNode>(() => {
     if (!context.draggable?.icon) {
@@ -290,6 +284,14 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
     );
   }, [isCheckable, checked, halfChecked, isDisabled, props.disableCheckbox, props.title]);
 
+  // ============== State: Node State (Open/Close) ==============
+  const nodeState = React.useMemo<typeof ICON_OPEN | typeof ICON_CLOSE>(() => {
+    if (memoizedIsLeaf) {
+      return null;
+    }
+    return expanded ? ICON_OPEN : ICON_CLOSE;
+  }, [memoizedIsLeaf, expanded]);
+
   // ==================== Render: Title + Icon ====================
   const iconNode = React.useMemo<React.ReactNode>(() => {
     return (

From ef683db6f8afb92d82a6fd5415c1639786f8aac0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Thu, 2 Jan 2025 17:29:47 +0800
Subject: [PATCH 377/397] 5.12.2

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 9a54bd05..61789aff 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.12.1",
+  "version": "5.12.2",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 310a427a142a0533c20b9bc80cf0a10b4cda0dd0 Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Thu, 2 Jan 2025 17:43:17 +0800
Subject: [PATCH 378/397] fix: fix type (#908)

---
 src/Tree.tsx | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index 44641eca..97209b7c 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -230,9 +230,10 @@ interface TreeState<TreeDataType extends BasicDataNode = DataNode> {
   fieldNames: FieldNames;
 }
 
-class Tree<
-  TreeDataType extends DataNode | BasicDataNode = DataNode | BasicDataNode,
-> extends React.Component<TreeProps<TreeDataType>, TreeState<TreeDataType>> {
+class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends React.Component<
+  TreeProps<TreeDataType>,
+  TreeState<TreeDataType>
+> {
   static defaultProps = {
     prefixCls: 'rc-tree',
     showLine: false,

From a7e7a601fa431c0a52e59f39d9be035b88f61301 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Thu, 2 Jan 2025 17:46:50 +0800
Subject: [PATCH 379/397] 5.12.3

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 61789aff..c0975f27 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.12.2",
+  "version": "5.12.3",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From eaf7d375eeaf07230035c32e6e99061d8843ae5f Mon Sep 17 00:00:00 2001
From: lijianan <574980606@qq.com>
Date: Thu, 2 Jan 2025 22:52:14 +0800
Subject: [PATCH 380/397] fix: fix renderSwitcher issue (#910)

---
 src/TreeNode.tsx | 21 ++++++++-------------
 1 file changed, 8 insertions(+), 13 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index ae2a4e94..919931f8 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -1,6 +1,6 @@
+import React from 'react';
 import classNames from 'classnames';
 import pickAttrs from 'rc-util/lib/pickAttrs';
-import React from 'react';
 import { TreeContext, UnstableContext } from './contextTypes';
 import Indent from './Indent';
 import type { TreeNodeProps } from './interface';
@@ -190,23 +190,18 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
     );
   }, [isLeaf, context.loadData, hasChildren, props.loaded]);
 
-  // Load data to avoid default expanded tree without data
-  // ============== Effect: Sync Load Data ==============
-  const syncLoadData = () => {
+  // ============== Effect ==============
+  React.useEffect(() => {
+    // Load data to avoid default expanded tree without data
     if (loading) {
       return;
     }
     // read from state to avoid loadData at same time
-    if (context.loadData && expanded && !memoizedIsLeaf && !props.loaded) {
+    if (typeof context.loadData === 'function' && expanded && !memoizedIsLeaf && !props.loaded) {
       // We needn't reload data when has children in sync logic
       // It's only needed in node expanded
       context.onNodeLoad(convertNodePropsToEventData(props));
     }
-  };
-
-  // ============== Effect ==============
-  React.useEffect(() => {
-    syncLoadData();
   }, [loading, context.loadData, context.onNodeLoad, expanded, memoizedIsLeaf, props]);
 
   // ==================== Render: Drag Handler ====================
@@ -228,7 +223,7 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
   };
 
   // Switcher
-  const switcherNode = React.useMemo<React.ReactNode>(() => {
+  const renderSwitcher = () => {
     if (memoizedIsLeaf) {
       // if switcherIconDom is null, no render switcher span
       const switcherIconDom = renderSwitcherIconDom(true);
@@ -255,7 +250,7 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
         {switcherIconDom}
       </span>
     ) : null;
-  }, [context.prefixCls, expanded, memoizedIsLeaf, props.switcherIcon, context.switcherIcon]);
+  };
 
   // ====================== Checkbox ======================
   const checkboxNode = React.useMemo<React.ReactNode>(() => {
@@ -452,7 +447,7 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
     >
       <Indent prefixCls={context.prefixCls} level={level} isStart={isStart} isEnd={isEnd} />
       {dragHandlerNode}
-      {switcherNode}
+      {renderSwitcher()}
       {checkboxNode}
       {selectorNode}
     </div>

From e72b1ae30791f3173cb950caf6be1c3ad2bbf518 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Thu, 2 Jan 2025 23:01:30 +0800
Subject: [PATCH 381/397] 5.12.4

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c0975f27..72b93410 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.12.3",
+  "version": "5.12.4",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 8495da4826c2a56a697313d7c571a6da4123de09 Mon Sep 17 00:00:00 2001
From: "lgtm-com[bot]" <43144390+lgtm-com[bot]@users.noreply.github.com>
Date: Thu, 2 Jan 2025 23:03:12 +0800
Subject: [PATCH 382/397] Add CodeQL workflow for GitHub code scanning (#660)

Co-authored-by: LGTM Migrator <lgtm-migrator@users.noreply.github.com>
---
 .github/workflows/codeql.yml | 41 ++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 .github/workflows/codeql.yml

diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 00000000..0722cb08
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,41 @@
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ "master" ]
+  pull_request:
+    branches: [ "master" ]
+  schedule:
+    - cron: "31 18 * * 4"
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ javascript ]
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Initialize CodeQL
+        uses: github/codeql-action/init@v2
+        with:
+          languages: ${{ matrix.language }}
+          queries: +security-and-quality
+
+      - name: Autobuild
+        uses: github/codeql-action/autobuild@v2
+
+      - name: Perform CodeQL Analysis
+        uses: github/codeql-action/analyze@v2
+        with:
+          category: "/language:${{ matrix.language }}"

From e54d7c6701253e1b1332a210c7f22159bf616ce9 Mon Sep 17 00:00:00 2001
From: afc163 <afc163@gmail.com>
Date: Thu, 2 Jan 2025 23:07:20 +0800
Subject: [PATCH 383/397] Delete .github/workflows/codeql.yml

---
 .github/workflows/codeql.yml | 41 ------------------------------------
 1 file changed, 41 deletions(-)
 delete mode 100644 .github/workflows/codeql.yml

diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
deleted file mode 100644
index 0722cb08..00000000
--- a/.github/workflows/codeql.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: "CodeQL"
-
-on:
-  push:
-    branches: [ "master" ]
-  pull_request:
-    branches: [ "master" ]
-  schedule:
-    - cron: "31 18 * * 4"
-
-jobs:
-  analyze:
-    name: Analyze
-    runs-on: ubuntu-latest
-    permissions:
-      actions: read
-      contents: read
-      security-events: write
-
-    strategy:
-      fail-fast: false
-      matrix:
-        language: [ javascript ]
-
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-
-      - name: Initialize CodeQL
-        uses: github/codeql-action/init@v2
-        with:
-          languages: ${{ matrix.language }}
-          queries: +security-and-quality
-
-      - name: Autobuild
-        uses: github/codeql-action/autobuild@v2
-
-      - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v2
-        with:
-          category: "/language:${{ matrix.language }}"

From 1dd1ececcaa71d84a0cd18b27800e2fe8848c2f2 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 2 Jan 2025 23:19:00 +0800
Subject: [PATCH 384/397] chore(deps-dev): bump less from 3.13.1 to 4.2.1
 (#911)

Bumps [less](https://github.com/less/less.js) from 3.13.1 to 4.2.1.
- [Release notes](https://github.com/less/less.js/releases)
- [Changelog](https://github.com/less/less.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/less/less.js/compare/v3.13.1...v4.2.1)

---
updated-dependencies:
- dependency-name: less
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: afc163 <afc163@gmail.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 72b93410..1e9e4e4c 100644
--- a/package.json
+++ b/package.json
@@ -67,7 +67,7 @@
     "gh-pages": "^6.1.1",
     "glob": "^7.1.6",
     "husky": "^9.1.6",
-    "less": "^3.11.1",
+    "less": "^4.2.1",
     "lint-staged": "^15.2.10",
     "np": "^10.0.5",
     "prettier": "^3.3.3",

From 8e1dd543635b74e9aca3c5dc7f57776f2f69cc51 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 2 Jan 2025 23:19:20 +0800
Subject: [PATCH 385/397] chore(deps-dev): bump glob from 7.2.3 to 11.0.0
 (#851)

Bumps [glob](https://github.com/isaacs/node-glob) from 7.2.3 to 11.0.0.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v7.2.3...v11.0.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: afc163 <afc163@gmail.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 1e9e4e4c..feb86f1a 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
     "eslint-plugin-unicorn": "^56.0.1",
     "father": "^4.4.0",
     "gh-pages": "^6.1.1",
-    "glob": "^7.1.6",
+    "glob": "^11.0.0",
     "husky": "^9.1.6",
     "less": "^4.2.1",
     "lint-staged": "^15.2.10",

From c62d5695c17f28948776290054735e531bd2eea6 Mon Sep 17 00:00:00 2001
From: EmilyyyLiu <100924403+EmilyyyLiu@users.noreply.github.com>
Date: Tue, 7 Jan 2025 11:17:23 +0800
Subject: [PATCH 386/397] feat: Add classname to distinguish between leaf nodes
 or parent nodes (#912)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat：给tree的叶子节点增加一个class，供使用者识别选中

* feat:增加节点标识

* feat：取消parent节点标记

* feat：添加测试文件，更新快照

* feat:let -> const

* feat：判断变量使用已有的

* feat:变量修改

* feat:修改快照

* feat:rc-tree-leaf改为rc-tree-treenode-leaf

* feat:修改单测

* feat:修改单测

---------

Co-authored-by: 刘欢 <lh01217311@antgroup.com>
---
 src/TreeNode.tsx                              |  1 +
 tests/Tree.spec.tsx                           | 23 +++++++
 tests/__snapshots__/Tree.spec.tsx.snap        | 42 ++++++-------
 .../__snapshots__/TreeNodeProps.spec.tsx.snap | 32 +++++-----
 tests/__snapshots__/TreeProps.spec.tsx.snap   | 62 +++++++++----------
 5 files changed, 92 insertions(+), 68 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 919931f8..20e1c3fa 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -430,6 +430,7 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
         'drag-over-gap-top': !isDisabled && dragOverGapTop,
         'drag-over-gap-bottom': !isDisabled && dragOverGapBottom,
         'filter-node': context.filterTreeNode?.(convertNodePropsToEventData(props)),
+        [`${context.prefixCls}-treenode-leaf`]: memoizedIsLeaf,
       })}
       style={style}
       // Draggable config
diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index 0ee77750..fe1125a7 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -1312,4 +1312,27 @@ describe('Tree Basic', () => {
       expect(getByRole('treeitem', { name: 'leaf 2' })).toHaveClass('rc-tree-treenode-disabled');
     });
   });
+  it('leaf className', () => {
+    const data = [
+      {
+        title: '0-0',
+        key: '0-0',
+        children: [
+          { title: '0-0-0', key: '0-0-0' },
+          {
+            title: '0-0-1',
+            key: '0-0-1',
+            children: [
+              { title: '0-0-1-0', key: '0-0-1-0' },
+              { title: '0-0-1-1', key: '0-0-1-1' },
+            ],
+          },
+        ],
+      },
+      { title: '0-1', key: '0-1' },
+    ];
+    const { container } = render(<Tree treeData={data} expandedKeys={['0-0', '0-0-1']} />);
+    const treeNodes = container.querySelectorAll('.rc-tree-treenode-leaf');
+    expect(treeNodes.length).toBe(4);
+  });
 });
diff --git a/tests/__snapshots__/Tree.spec.tsx.snap b/tests/__snapshots__/Tree.spec.tsx.snap
index 0385139a..60bd9998 100644
--- a/tests/__snapshots__/Tree.spec.tsx.snap
+++ b/tests/__snapshots__/Tree.spec.tsx.snap
@@ -73,7 +73,7 @@ exports[`Tree Basic check basic render 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -111,7 +111,7 @@ exports[`Tree Basic check basic render 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -226,7 +226,7 @@ exports[`Tree Basic check check after data ready works 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -308,7 +308,7 @@ exports[`Tree Basic check check update when Tree trigger componentWillReceivePro
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-checkbox-checked rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -386,7 +386,7 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -413,7 +413,7 @@ exports[`Tree Basic ignore illegal node as Tree children Direct TreeNode 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -485,7 +485,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -539,7 +539,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -570,7 +570,7 @@ exports[`Tree Basic ignore illegal node as Tree children Sub TreeNode 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -717,7 +717,7 @@ exports[`Tree Basic renders correctly 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -757,7 +757,7 @@ exports[`Tree Basic renders correctly 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -797,7 +797,7 @@ exports[`Tree Basic renders correctly 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -953,7 +953,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -980,7 +980,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1007,7 +1007,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1034,7 +1034,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1061,7 +1061,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1088,7 +1088,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1115,7 +1115,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1142,7 +1142,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1169,7 +1169,7 @@ exports[`Tree Basic should support rootStyle and rootClassName 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
diff --git a/tests/__snapshots__/TreeNodeProps.spec.tsx.snap b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
index d8a4b849..4bf68f3f 100644
--- a/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
+++ b/tests/__snapshots__/TreeNodeProps.spec.tsx.snap
@@ -98,7 +98,7 @@ exports[`TreeNode Props className 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -132,7 +132,7 @@ exports[`TreeNode Props className 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -163,7 +163,7 @@ exports[`TreeNode Props className 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -235,7 +235,7 @@ exports[`TreeNode Props customize icon component 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -311,7 +311,7 @@ exports[`TreeNode Props customize icon element 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -387,7 +387,7 @@ exports[`TreeNode Props customize icon hide icon 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -517,7 +517,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
           <div
             aria-expanded="false"
             aria-label="0-0-0-0"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -552,7 +552,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
           <div
             aria-expanded="false"
             aria-label="0-0-1"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -584,7 +584,7 @@ exports[`TreeNode Props data and aria props renders aria attributes on li 1`] =
           <div
             aria-expanded="false"
             aria-label="0-1"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -716,7 +716,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             data-test="0-0-0-0"
             draggable="false"
             role="treeitem"
@@ -751,7 +751,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             data-test="0-0-1"
             draggable="false"
             role="treeitem"
@@ -783,7 +783,7 @@ exports[`TreeNode Props data and aria props renders data attributes on li 1`] =
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             data-test="0-1"
             draggable="false"
             role="treeitem"
@@ -855,7 +855,7 @@ exports[`TreeNode Props isLeaf 1`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            class="rc-tree-treenode rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -878,7 +878,7 @@ exports[`TreeNode Props isLeaf 1`] = `
             </span>
           </div>
           <div
-            class="rc-tree-treenode rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -950,7 +950,7 @@ exports[`TreeNode Props isLeaf 2`] = `
           style="display: flex; flex-direction: column;"
         >
           <div
-            class="rc-tree-treenode rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -976,7 +976,7 @@ exports[`TreeNode Props isLeaf 2`] = `
             </span>
           </div>
           <div
-            class="rc-tree-treenode rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
diff --git a/tests/__snapshots__/TreeProps.spec.tsx.snap b/tests/__snapshots__/TreeProps.spec.tsx.snap
index 0fdc31b9..6a001df6 100644
--- a/tests/__snapshots__/TreeProps.spec.tsx.snap
+++ b/tests/__snapshots__/TreeProps.spec.tsx.snap
@@ -73,7 +73,7 @@ exports[`Tree Props checkStrictly 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -188,7 +188,7 @@ exports[`Tree Props checkable default 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -303,7 +303,7 @@ exports[`Tree Props checkable without selectable 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -385,7 +385,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -443,7 +443,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -474,7 +474,7 @@ exports[`Tree Props custom switcher icon switcher icon 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -550,7 +550,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -581,7 +581,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -639,7 +639,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -674,7 +674,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -709,7 +709,7 @@ exports[`Tree Props custom switcher icon switcher leaf icon 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -902,7 +902,7 @@ exports[`Tree Props defaultExpandAll 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -979,7 +979,7 @@ exports[`Tree Props disabled basic 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1052,7 +1052,7 @@ exports[`Tree Props disabled treeNode should disabled when tree disabled 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-disabled rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1155,7 +1155,7 @@ exports[`Tree Props icon 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1268,7 +1268,7 @@ exports[`Tree Props invalidate checkedKeys null 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1383,7 +1383,7 @@ exports[`Tree Props invalidate checkedKeys number 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1589,7 +1589,7 @@ exports[`Tree Props multiple 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1780,7 +1780,7 @@ exports[`Tree Props selectable with selectable 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1883,7 +1883,7 @@ exports[`Tree Props selectable without selectable 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -1986,7 +1986,7 @@ exports[`Tree Props showIcon 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2082,7 +2082,7 @@ exports[`Tree Props showIcon 2`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2213,7 +2213,7 @@ exports[`Tree Props showIcon 3`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2247,7 +2247,7 @@ exports[`Tree Props showIcon 3`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2278,7 +2278,7 @@ exports[`Tree Props showIcon 3`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2394,7 +2394,7 @@ exports[`Tree Props treeData 1`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2448,7 +2448,7 @@ exports[`Tree Props treeData 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2510,7 +2510,7 @@ exports[`Tree Props treeData 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2544,7 +2544,7 @@ exports[`Tree Props treeData 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2578,7 +2578,7 @@ exports[`Tree Props treeData 1`] = `
           </div>
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >
@@ -2654,7 +2654,7 @@ exports[`Tree Props treeData 2`] = `
         >
           <div
             aria-expanded="false"
-            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last"
+            class="rc-tree-treenode rc-tree-treenode-switcher-close rc-tree-treenode-leaf-last rc-tree-treenode-leaf"
             draggable="false"
             role="treeitem"
           >

From c157a57693efae1212d2fe3699c814255cff4bab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 7 Jan 2025 11:23:01 +0800
Subject: [PATCH 387/397] 5.13.0

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index feb86f1a..24c2577d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.12.4",
+  "version": "5.13.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 3b1dcb08ccd8691f92b85adf8948fd86c4085d8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?=
 =?UTF-8?q?=E5=8D=9C?= <smith3816@gmail.com>
Date: Tue, 25 Feb 2025 16:53:28 +0800
Subject: [PATCH 388/397] Fix context (#925)

* test: test driven

* fix: context disabled not update
---
 src/TreeNode.tsx    |  4 +---
 tests/Tree.spec.tsx | 29 +++++++++++++++++++++++++++++
 2 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 20e1c3fa..278aefde 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -47,9 +47,7 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
   const [dragNodeHighlight, setDragNodeHighlight] = React.useState<boolean>(false);
 
   // ======= State: Disabled State =======
-  const isDisabled = React.useMemo<boolean>(() => {
-    return !!(context.disabled || props.disabled || unstableContext.nodeDisabled?.(data));
-  }, [context.disabled, props.disabled, unstableContext.nodeDisabled, data]);
+  const isDisabled = !!(context.disabled || props.disabled || unstableContext.nodeDisabled?.(data));
 
   const isCheckable = React.useMemo<React.ReactNode>(() => {
     // Return false if tree or treeNode is not checkable
diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index fe1125a7..31a4d4c9 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -1311,6 +1311,35 @@ describe('Tree Basic', () => {
       expect(getByRole('treeitem', { name: 'leaf 1' })).toHaveClass('rc-tree-treenode-disabled');
       expect(getByRole('treeitem', { name: 'leaf 2' })).toHaveClass('rc-tree-treenode-disabled');
     });
+
+    it('correct handle closure', () => {
+      let disabled = false;
+      const nodeDisabled = () => disabled;
+
+      const singleTreeData = [
+        {
+          title: '0',
+          key: '0',
+        },
+      ];
+
+      const renderDemo = () => (
+        <UnstableContext.Provider
+          value={{
+            nodeDisabled,
+          }}
+        >
+          <Tree defaultExpandAll treeData={singleTreeData} />
+        </UnstableContext.Provider>
+      );
+
+      const { container, rerender } = render(renderDemo());
+      expect(container.querySelector('.rc-tree-treenode-disabled')).toBeFalsy();
+
+      disabled = true;
+      rerender(renderDemo());
+      expect(container.querySelector('.rc-tree-treenode-disabled')).toBeTruthy();
+    });
   });
   it('leaf className', () => {
     const data = [

From 08f21b0148cc08f43dfb37fd25b1a0022b8f1b7d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Tue, 25 Feb 2025 16:54:31 +0800
Subject: [PATCH 389/397] 5.13.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 24c2577d..1172f44c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rc-tree",
-  "version": "5.13.0",
+  "version": "5.13.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From 4ce3d40c94dfc7857bccb0a1287119343ea9b6d3 Mon Sep 17 00:00:00 2001
From: Jony J <1844749591@qq.com>
Date: Wed, 5 Mar 2025 17:56:50 +0800
Subject: [PATCH 390/397] refactor: migrate to rc scope (#926)

* refactor: migrate to rc scope

* chore: revert

* chore: not blame

* chore: not blame
---
 .github/dependabot.yml                 | 12 +++++-------
 docs/examples/animation-draggable.jsx  |  2 +-
 docs/examples/animation.jsx            |  4 ++--
 docs/examples/basic-controlled.jsx     |  6 +++---
 docs/examples/basic.jsx                |  2 +-
 docs/examples/big-data.jsx             |  2 +-
 docs/examples/contextmenu.jsx          |  4 ++--
 docs/examples/custom-switch-icon.jsx   |  2 +-
 docs/examples/draggable-allow-drop.jsx |  2 +-
 docs/examples/dropdown.jsx             |  4 ++--
 docs/examples/dynamic.jsx              |  2 +-
 docs/examples/expandAction.jsx         |  2 +-
 docs/examples/fieldNames.tsx           |  2 +-
 docs/examples/funtionTitle.jsx         |  2 +-
 docs/examples/icon.jsx                 |  2 +-
 docs/examples/selectable.jsx           |  3 +--
 package.json                           | 21 ++++++++++-----------
 src/MotionTreeNode.tsx                 |  4 ++--
 src/NodeList.tsx                       |  2 +-
 src/Tree.tsx                           |  6 +++---
 src/TreeNode.tsx                       |  2 +-
 src/useUnmount.ts                      |  2 +-
 src/util.tsx                           |  2 +-
 src/utils/conductUtil.ts               |  2 +-
 src/utils/treeUtil.ts                  |  6 +++---
 tests/Accessibility.spec.tsx           |  2 +-
 tests/Tree.spec.tsx                    |  4 ++--
 tests/TreeDraggable.spec.tsx           |  2 +-
 tests/TreeMotion.spec.tsx              |  4 ++--
 tests/TreeProps.spec.tsx               |  2 +-
 tsconfig.json                          | 16 +++-------------
 31 files changed, 58 insertions(+), 72 deletions(-)

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index afd4cd08..437d0bd3 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -18,14 +18,12 @@ updates:
     - 17.0.0
     - 17.0.1
     - 17.0.2
-  - dependency-name: np
+  - dependency-name: '@rc-component/np'
     versions:
-    - 7.2.0
-    - 7.3.0
-    - 7.4.0
-  - dependency-name: rc-tooltip
+    - 1.0.0
+  - dependency-name: '@rc-component/tooltip'
     versions:
-    - 5.0.2
+    - 1.0.0
   - dependency-name: less
     versions:
-    - 4.1.0
+    - 4.1.0
\ No newline at end of file
diff --git a/docs/examples/animation-draggable.jsx b/docs/examples/animation-draggable.jsx
index 0448d7e4..cb298554 100644
--- a/docs/examples/animation-draggable.jsx
+++ b/docs/examples/animation-draggable.jsx
@@ -3,7 +3,7 @@ react/no-danger, no-param-reassign */
 import React from 'react';
 import { gData } from './utils/dataUtil';
 import '../../assets/index.less';
-import Tree from 'rc-tree';
+import Tree from '@rc-component/tree';
 
 const STYLE = `
 .rc-tree-child-tree {
diff --git a/docs/examples/animation.jsx b/docs/examples/animation.jsx
index 82d345a0..7dcb5c3f 100644
--- a/docs/examples/animation.jsx
+++ b/docs/examples/animation.jsx
@@ -1,6 +1,6 @@
 /* eslint no-console:0, react/no-danger: 0 */
-import { Provider } from 'rc-motion';
-import Tree from 'rc-tree';
+import { Provider } from '@rc-component/motion';
+import Tree from '@rc-component/tree';
 import React from 'react';
 import '../../assets/index.less';
 import './animation.less';
diff --git a/docs/examples/basic-controlled.jsx b/docs/examples/basic-controlled.jsx
index cbbbce00..8a75cb76 100644
--- a/docs/examples/basic-controlled.jsx
+++ b/docs/examples/basic-controlled.jsx
@@ -1,9 +1,9 @@
 /* eslint-disable no-console, react/no-unescaped-entities */
 import '../../assets/index.less';
 import React from 'react';
-import 'rc-dialog/assets/index.css';
-import Modal from 'rc-dialog';
-import Tree, { TreeNode } from 'rc-tree';
+import '@rc-component/dialog/assets/index.css';
+import Modal from '@rc-component/dialog';
+import Tree, { TreeNode } from '@rc-component/tree';
 import { gData, getRadioSelectKeys } from './utils/dataUtil';
 
 class Demo extends React.Component {
diff --git a/docs/examples/basic.jsx b/docs/examples/basic.jsx
index ab986e32..55e96fc5 100644
--- a/docs/examples/basic.jsx
+++ b/docs/examples/basic.jsx
@@ -2,7 +2,7 @@
 import React from 'react';
 import '../../assets/index.less';
 import './basic.less';
-import Tree, { TreeNode } from 'rc-tree';
+import Tree, { TreeNode } from '@rc-component/tree';
 
 const treeData = [
   {
diff --git a/docs/examples/big-data.jsx b/docs/examples/big-data.jsx
index 657358ac..64c67fec 100644
--- a/docs/examples/big-data.jsx
+++ b/docs/examples/big-data.jsx
@@ -2,7 +2,7 @@
 import React from 'react';
 import Gen from './big-data-generator';
 import '../../assets/index.less';
-import Tree, { TreeNode } from 'rc-tree';
+import Tree, { TreeNode } from '@rc-component/tree';
 
 class Demo extends React.Component {
   state = {
diff --git a/docs/examples/contextmenu.jsx b/docs/examples/contextmenu.jsx
index 5f8c46f5..06507e0e 100644
--- a/docs/examples/contextmenu.jsx
+++ b/docs/examples/contextmenu.jsx
@@ -1,10 +1,10 @@
 /* eslint-disable no-console, react/no-find-dom-node */
 import React from 'react';
 import ReactDOM from 'react-dom';
-import Tooltip from 'rc-tooltip';
+import Tooltip from '@rc-component/tooltip';
 import './contextmenu.less';
 import '../../assets/index.less';
-import Tree, { TreeNode } from 'rc-tree';
+import Tree, { TreeNode } from '@rc-component/tree';
 
 function contains(root, n) {
   let node = n;
diff --git a/docs/examples/custom-switch-icon.jsx b/docs/examples/custom-switch-icon.jsx
index e783d608..6d5ed4d4 100644
--- a/docs/examples/custom-switch-icon.jsx
+++ b/docs/examples/custom-switch-icon.jsx
@@ -2,7 +2,7 @@
 /* eslint no-alert:0 */
 import React from 'react';
 import '../../assets/index.less';
-import Tree, { TreeNode } from 'rc-tree';
+import Tree, { TreeNode } from '@rc-component/tree';
 
 const arrowPath =
   'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h-88' +
diff --git a/docs/examples/draggable-allow-drop.jsx b/docs/examples/draggable-allow-drop.jsx
index fdacfa35..8c661ae3 100644
--- a/docs/examples/draggable-allow-drop.jsx
+++ b/docs/examples/draggable-allow-drop.jsx
@@ -3,7 +3,7 @@ import React from 'react';
 import { gData } from './utils/dataUtil';
 import './draggable.less';
 import '../../assets/index.less';
-import Tree from 'rc-tree';
+import Tree from '@rc-component/tree';
 
 function allowDrop({ dropNode, dropPosition }) {
   if (!dropNode.children) {
diff --git a/docs/examples/dropdown.jsx b/docs/examples/dropdown.jsx
index ac5add32..e7ab29d5 100644
--- a/docs/examples/dropdown.jsx
+++ b/docs/examples/dropdown.jsx
@@ -2,11 +2,11 @@
 /* eslint no-console:0 */
 /* eslint react/no-string-refs:0 */
 import React from 'react';
-import Trigger from 'rc-trigger';
+import Trigger from '@rc-component/trigger';
 import { gData } from './utils/dataUtil';
 import './dropdown.less';
 import '../../assets/index.less';
-import Tree, { TreeNode } from 'rc-tree';
+import Tree, { TreeNode } from '@rc-component/tree';
 
 const placements = {
   topLeft: {
diff --git a/docs/examples/dynamic.jsx b/docs/examples/dynamic.jsx
index 90fa1606..35c8f5f4 100644
--- a/docs/examples/dynamic.jsx
+++ b/docs/examples/dynamic.jsx
@@ -1,7 +1,7 @@
 /* eslint-disable no-console, react/no-access-state-in-setstate */
 import '../../assets/index.less';
 import React from 'react';
-import Tree from 'rc-tree';
+import Tree from '@rc-component/tree';
 
 function generateTreeNodes(treeNode) {
   const arr = [];
diff --git a/docs/examples/expandAction.jsx b/docs/examples/expandAction.jsx
index e6482d2c..a735a473 100644
--- a/docs/examples/expandAction.jsx
+++ b/docs/examples/expandAction.jsx
@@ -1,7 +1,7 @@
 /* eslint-disable no-console, react/no-access-state-in-setstate */
 import React from 'react';
 import '../../assets/index.less';
-import Tree, { TreeNode } from 'rc-tree';
+import Tree, { TreeNode } from '@rc-component/tree';
 
 const Demo = () => (
   <div className="expandAction-demo">
diff --git a/docs/examples/fieldNames.tsx b/docs/examples/fieldNames.tsx
index 2f6ce4cf..70b350fe 100644
--- a/docs/examples/fieldNames.tsx
+++ b/docs/examples/fieldNames.tsx
@@ -2,7 +2,7 @@
 import React from 'react';
 import '../../assets/index.less';
 import './basic.less';
-import Tree from 'rc-tree';
+import Tree from '@rc-component/tree';
 
 const treeData = [
   {
diff --git a/docs/examples/funtionTitle.jsx b/docs/examples/funtionTitle.jsx
index 338e5846..4c6560c8 100644
--- a/docs/examples/funtionTitle.jsx
+++ b/docs/examples/funtionTitle.jsx
@@ -2,7 +2,7 @@
 import '../../assets/index.less';
 import './animation.less';
 import React, { useState } from 'react';
-import Tree from 'rc-tree';
+import Tree from '@rc-component/tree';
 import data from './longData.json';
 
 const STYLE = `
diff --git a/docs/examples/icon.jsx b/docs/examples/icon.jsx
index 200f4361..63e42807 100644
--- a/docs/examples/icon.jsx
+++ b/docs/examples/icon.jsx
@@ -2,7 +2,7 @@
 /* eslint no-alert:0 */
 import React from 'react';
 import classNames from 'classnames';
-import Tree, { TreeNode } from 'rc-tree';
+import Tree, { TreeNode } from '@rc-component/tree';
 import '../../assets/index.less';
 import './icon.less';
 
diff --git a/docs/examples/selectable.jsx b/docs/examples/selectable.jsx
index e45dc833..bcde39f7 100644
--- a/docs/examples/selectable.jsx
+++ b/docs/examples/selectable.jsx
@@ -2,10 +2,9 @@
 import React from 'react';
 import './selectable.less';
 import '../../assets/index.less';
-import Tree, { TreeNode } from 'rc-tree';
+import Tree, { TreeNode } from '@rc-component/tree';
 
 class Demo extends React.Component {
-
   render() {
     return (
       <div className="selectable-demo">
diff --git a/package.json b/package.json
index 1172f44c..d5a37fdb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
-  "name": "rc-tree",
-  "version": "5.13.1",
+  "name": "@rc-component/tree",
+  "version": "1.0.0",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"
@@ -34,7 +34,7 @@
     "docs:deploy": "gh-pages -d dist",
     "compile": "father build && lessc assets/index.less assets/index.css",
     "prepare": "husky",
-    "prepublishOnly": "npm run compile && np --yolo --no-publish",
+    "prepublishOnly": "npm run compile && rc-np",
     "postpublish": "npm run gh-pages",
     "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js",
     "test": "rc-test",
@@ -50,7 +50,7 @@
     "react-dom": "*"
   },
   "devDependencies": {
-    "@rc-component/father-plugin": "^1.0.2",
+    "@rc-component/father-plugin": "^2.0.3",
     "@testing-library/jest-dom": "^6.1.5",
     "@testing-library/react": "^16.1.0",
     "@types/jest": "^29.5.10",
@@ -69,21 +69,20 @@
     "husky": "^9.1.6",
     "less": "^4.2.1",
     "lint-staged": "^15.2.10",
-    "np": "^10.0.5",
+    "@rc-component/np": "^1.0.0",
     "prettier": "^3.3.3",
-    "rc-dialog": "^9.0.0",
+    "@rc-component/dialog": "^1.0.0",
     "rc-test": "^7.0.15",
-    "rc-tooltip": "^6.3.2",
-    "rc-trigger": "^5.0.7",
+    "@rc-component/tooltip": "^1.0.0",
+    "@rc-component/trigger": "^3.0.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "typescript": "^5.3.3"
   },
   "dependencies": {
-    "@babel/runtime": "^7.10.1",
     "classnames": "2.x",
-    "rc-motion": "^2.0.1",
-    "rc-util": "^5.16.1",
+    "@rc-component/motion": "^1.0.0",
+    "@rc-component/util": "^1.2.1",
     "rc-virtual-list": "^3.5.1"
   }
 }
diff --git a/src/MotionTreeNode.tsx b/src/MotionTreeNode.tsx
index 882c56fb..946289c6 100644
--- a/src/MotionTreeNode.tsx
+++ b/src/MotionTreeNode.tsx
@@ -1,6 +1,6 @@
 import classNames from 'classnames';
-import CSSMotion from 'rc-motion';
-import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
+import CSSMotion from '@rc-component/motion';
+import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
 import * as React from 'react';
 import { TreeContext } from './contextTypes';
 import type { FlattenNode, TreeNodeProps } from './interface';
diff --git a/src/NodeList.tsx b/src/NodeList.tsx
index 2106ad55..94d4dea6 100644
--- a/src/NodeList.tsx
+++ b/src/NodeList.tsx
@@ -2,7 +2,7 @@
  * Handle virtual list of the TreeNodes.
  */
 
-import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
+import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
 import VirtualList, { type ListRef } from 'rc-virtual-list';
 import * as React from 'react';
 import MotionTreeNode from './MotionTreeNode';
diff --git a/src/Tree.tsx b/src/Tree.tsx
index 97209b7c..b5726532 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -2,9 +2,9 @@
 // Fully accessibility support
 
 import classNames from 'classnames';
-import KeyCode from 'rc-util/lib/KeyCode';
-import pickAttrs from 'rc-util/lib/pickAttrs';
-import warning from 'rc-util/lib/warning';
+import KeyCode from '@rc-component/util/lib/KeyCode';
+import pickAttrs from '@rc-component/util/lib/pickAttrs';
+import warning from '@rc-component/util/lib/warning';
 import * as React from 'react';
 
 import type {
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 278aefde..4e53224f 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import classNames from 'classnames';
-import pickAttrs from 'rc-util/lib/pickAttrs';
+import pickAttrs from '@rc-component/util/lib/pickAttrs';
 import { TreeContext, UnstableContext } from './contextTypes';
 import Indent from './Indent';
 import type { TreeNodeProps } from './interface';
diff --git a/src/useUnmount.ts b/src/useUnmount.ts
index a07b4161..b113c4ed 100644
--- a/src/useUnmount.ts
+++ b/src/useUnmount.ts
@@ -1,5 +1,5 @@
 import * as React from 'react';
-import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
+import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
 
 /**
  * Trigger only when component unmount
diff --git a/src/util.tsx b/src/util.tsx
index 53e4af03..0aa3c03c 100644
--- a/src/util.tsx
+++ b/src/util.tsx
@@ -3,7 +3,7 @@
  * Legacy code. Should avoid to use if you are new to import these code.
  */
 
-import warning from 'rc-util/lib/warning';
+import warning from '@rc-component/util/lib/warning';
 import React from 'react';
 import type {
   BasicDataNode,
diff --git a/src/utils/conductUtil.ts b/src/utils/conductUtil.ts
index bd9a0ff6..c45e4d74 100644
--- a/src/utils/conductUtil.ts
+++ b/src/utils/conductUtil.ts
@@ -1,4 +1,4 @@
-import warning from 'rc-util/lib/warning';
+import warning from '@rc-component/util/lib/warning';
 import type {
   BasicDataNode,
   DataEntity,
diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts
index 95a2f72f..ec895946 100644
--- a/src/utils/treeUtil.ts
+++ b/src/utils/treeUtil.ts
@@ -1,6 +1,6 @@
-import toArray from 'rc-util/lib/Children/toArray';
-import omit from 'rc-util/lib/omit';
-import warning from 'rc-util/lib/warning';
+import toArray from '@rc-component/util/lib/Children/toArray';
+import omit from '@rc-component/util/lib/omit';
+import warning from '@rc-component/util/lib/warning';
 import * as React from 'react';
 import type {
   BasicDataNode,
diff --git a/tests/Accessibility.spec.tsx b/tests/Accessibility.spec.tsx
index a66fff7e..739dd617 100644
--- a/tests/Accessibility.spec.tsx
+++ b/tests/Accessibility.spec.tsx
@@ -1,6 +1,6 @@
 /* eslint-disable no-undef, react/no-multi-comp */
 import { fireEvent, render } from '@testing-library/react';
-import KeyCode from 'rc-util/lib/KeyCode';
+import KeyCode from '@rc-component/util/lib/KeyCode';
 import React from 'react';
 import Tree, { FieldDataNode } from '../src';
 import { spyConsole } from './util';
diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index 31a4d4c9..25e4eba5 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -2,8 +2,8 @@
 react/no-unused-state, react/prop-types, no-return-assign */
 import React from 'react';
 import { render, fireEvent, act } from '@testing-library/react';
-import { resetWarned } from 'rc-util/lib/warning';
-import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
+import { resetWarned } from '@rc-component/util/lib/warning';
+import { spyElementPrototypes } from '@rc-component/util/lib/test/domHook';
 import Tree, { TreeNode } from '../src';
 import { objectMatcher, spyConsole, spyError } from './util';
 import { UnstableContext } from '../src';
diff --git a/tests/TreeDraggable.spec.tsx b/tests/TreeDraggable.spec.tsx
index d8876fd8..5fb082d2 100644
--- a/tests/TreeDraggable.spec.tsx
+++ b/tests/TreeDraggable.spec.tsx
@@ -1,7 +1,7 @@
 /* eslint-disable no-undef, react/no-multi-comp, no-console,
 react/no-unused-state, react/prop-types, no-return-assign */
 import { act, createEvent, fireEvent, render } from '@testing-library/react';
-import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
+import { spyElementPrototypes } from '@rc-component/util/lib/test/domHook';
 import React from 'react';
 import Tree, { FieldDataNode, TreeNode, TreeProps } from '../src';
 import { spyConsole } from './util';
diff --git a/tests/TreeMotion.spec.tsx b/tests/TreeMotion.spec.tsx
index 487f34e2..7cbcb91b 100644
--- a/tests/TreeMotion.spec.tsx
+++ b/tests/TreeMotion.spec.tsx
@@ -5,8 +5,8 @@ import { TreeContext } from '../src/contextTypes';
 import MotionTreeNode from '../src/MotionTreeNode';
 import { getMinimumRangeTransitionRange } from '../src/NodeList';
 
-jest.mock('rc-motion/lib/util/motion', () => {
-  const origin = jest.requireActual('rc-motion/lib/util/motion');
+jest.mock('@rc-component/motion/lib/util/motion', () => {
+  const origin = jest.requireActual('@rc-component/motion/lib/util/motion');
 
   return {
     ...origin,
diff --git a/tests/TreeProps.spec.tsx b/tests/TreeProps.spec.tsx
index d6b1038f..1e02cb4f 100644
--- a/tests/TreeProps.spec.tsx
+++ b/tests/TreeProps.spec.tsx
@@ -1,6 +1,6 @@
 /* eslint-disable no-undef, react/no-multi-comp */
 import { act, fireEvent, render } from '@testing-library/react';
-import { resetWarned } from 'rc-util/lib/warning';
+import { resetWarned } from '@rc-component/util/lib/warning';
 import React from 'react';
 import Tree, { FieldDataNode, TreeNode } from '../src';
 import { objectMatcher, spyConsole, spyError } from './util';
diff --git a/tsconfig.json b/tsconfig.json
index fe2e24f4..38b8cdfd 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,23 +7,13 @@
     "declaration": true,
     "skipLibCheck": true,
     "esModuleInterop": true,
-    "paths": {
-      "@/*": [
-        "src/*"
-      ],
-      "@@/*": [
-        "src/.umi/*"
-      ],
-      "rc-tree": [
-        "src/index.ts"
-      ]
-    }
+    "paths": { "@/*": ["src/*"], "@@/*": ["src/.umi/*"], "@rc-component/tree": ["src/index.ts"] }
   },
   "include": [
     ".dumirc.ts",
     "./src/**/*.ts",
     "./src/**/*.tsx",
     "./docs/**/*.tsx",
-    "./tests/**/*.tsx",
+    "./tests/**/*.tsx"
   ]
-}
\ No newline at end of file
+}

From ccbf54a2b4dd7c4147e519e72d62e5acb3180992 Mon Sep 17 00:00:00 2001
From: thinkasany <480968828@qq.com>
Date: Wed, 19 Mar 2025 11:51:36 +0800
Subject: [PATCH 391/397] feat: support classNames and styles (#928)

* feat: support classNames and styles

* fix

* update
---
 src/Tree.tsx        |  7 +++++++
 src/TreeNode.tsx    | 16 ++++++++++++---
 src/contextTypes.ts |  4 +++-
 tests/Tree.spec.tsx | 47 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 70 insertions(+), 4 deletions(-)

diff --git a/src/Tree.tsx b/src/Tree.tsx
index b5726532..ec675df9 100644
--- a/src/Tree.tsx
+++ b/src/Tree.tsx
@@ -83,10 +83,13 @@ export type DraggableConfig = {
 
 export type ExpandAction = false | 'click' | 'doubleClick';
 
+export type SemanticName = 'itemIcon' | 'item' | 'itemTitle';
 export interface TreeProps<TreeDataType extends BasicDataNode = DataNode> {
   prefixCls: string;
   className?: string;
   style?: React.CSSProperties;
+  styles?: Partial<Record<SemanticName, React.CSSProperties>>;
+  classNames?: Partial<Record<SemanticName, string>>;
   focusable?: boolean;
   activeKey?: Key | null;
   tabIndex?: number;
@@ -1363,6 +1366,8 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
       prefixCls,
       className,
       style,
+      styles,
+      classNames: treeClassNames,
       showLine,
       focusable,
       tabIndex = 0,
@@ -1410,6 +1415,8 @@ class Tree<TreeDataType extends DataNode | BasicDataNode = DataNode> extends Rea
     }
 
     const contextValue = {
+      styles,
+      classNames: treeClassNames,
       prefixCls,
       selectable,
       showIcon,
diff --git a/src/TreeNode.tsx b/src/TreeNode.tsx
index 4e53224f..06c648e7 100644
--- a/src/TreeNode.tsx
+++ b/src/TreeNode.tsx
@@ -39,6 +39,7 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
   } = props;
 
   const context = React.useContext(TreeContext);
+  const { classNames: treeClassNames, styles } = context || {};
 
   const unstableContext = React.useContext(UnstableContext);
 
@@ -290,10 +291,12 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
     return (
       <span
         className={classNames(
+          treeClassNames?.itemIcon,
           `${context.prefixCls}-iconEle`,
           `${context.prefixCls}-icon__${nodeState || 'docu'}`,
           { [`${context.prefixCls}-icon_loading`]: loading },
         )}
+        style={styles?.itemIcon}
       />
     );
   }, [context.prefixCls, nodeState, loading]);
@@ -339,9 +342,11 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
       $icon = currentIcon ? (
         <span
           className={classNames(
+            treeClassNames?.itemIcon,
             `${context.prefixCls}-iconEle`,
             `${context.prefixCls}-icon__customize`,
           )}
+          style={styles?.itemIcon}
         >
           {typeof currentIcon === 'function' ? currentIcon(props) : currentIcon}
         </span>
@@ -376,7 +381,12 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
         onDoubleClick={onSelectorDoubleClick}
       >
         {$icon}
-        <span className={`${context.prefixCls}-title`}>{titleNode}</span>
+        <span
+          className={classNames(`${context.prefixCls}-title`, treeClassNames?.itemTitle)}
+          style={styles?.itemTitle}
+        >
+          {titleNode}
+        </span>
         {dropIndicatorNode}
       </span>
     );
@@ -411,7 +421,7 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
       ref={domRef}
       role="treeitem"
       aria-expanded={isLeaf ? undefined : expanded}
-      className={classNames(className, `${context.prefixCls}-treenode`, {
+      className={classNames(className, `${context.prefixCls}-treenode`, treeClassNames?.item, {
         [`${context.prefixCls}-treenode-disabled`]: isDisabled,
         [`${context.prefixCls}-treenode-switcher-${expanded ? 'open' : 'close'}`]: !isLeaf,
         [`${context.prefixCls}-treenode-checkbox-checked`]: checked,
@@ -430,7 +440,7 @@ const TreeNode: React.FC<Readonly<TreeNodeProps>> = props => {
         'filter-node': context.filterTreeNode?.(convertNodePropsToEventData(props)),
         [`${context.prefixCls}-treenode-leaf`]: memoizedIsLeaf,
       })}
-      style={style}
+      style={{ ...style, ...styles?.item }}
       // Draggable config
       draggable={draggableWithoutDisabled}
       onDragStart={draggableWithoutDisabled ? onDragStart : undefined}
diff --git a/src/contextTypes.ts b/src/contextTypes.ts
index 0c525b38..b41e037a 100644
--- a/src/contextTypes.ts
+++ b/src/contextTypes.ts
@@ -13,7 +13,7 @@ import type {
   KeyEntities,
   TreeNodeProps,
 } from './interface';
-import type { DraggableConfig } from './Tree';
+import type { DraggableConfig, SemanticName } from './Tree';
 
 export type NodeMouseEventParams<
   TreeDataType extends BasicDataNode = DataNode,
@@ -40,6 +40,8 @@ export type NodeDragEventHandler<
 > = (e: React.DragEvent<T>, nodeProps: TreeNodeProps<TreeDataType>, outsideTree?: boolean) => void;
 
 export interface TreeContextProps<TreeDataType extends BasicDataNode = DataNode> {
+  styles?: Partial<Record<SemanticName, React.CSSProperties>>;
+  classNames?: Partial<Record<SemanticName, string>>;
   prefixCls: string;
   selectable: boolean;
   showIcon: boolean;
diff --git a/tests/Tree.spec.tsx b/tests/Tree.spec.tsx
index 25e4eba5..fd8f6e35 100644
--- a/tests/Tree.spec.tsx
+++ b/tests/Tree.spec.tsx
@@ -1364,4 +1364,51 @@ describe('Tree Basic', () => {
     const treeNodes = container.querySelectorAll('.rc-tree-treenode-leaf');
     expect(treeNodes.length).toBe(4);
   });
+  it('support classNames and styles', () => {
+    const data = [
+      {
+        title: '0-0',
+        key: '0-0',
+        children: [
+          { title: '0-0-0', key: '0-0-0' },
+          {
+            title: '0-0-1',
+            key: '0-0-1',
+            children: [
+              { title: '0-0-1-0', key: '0-0-1-0' },
+              { title: '0-0-1-1', key: '0-0-1-1' },
+            ],
+          },
+        ],
+      },
+      { title: '0-1', key: '0-1' },
+    ];
+    const testClassNames = {
+      item: 'test-item',
+      itemIcon: 'test-icon',
+      itemTitle: 'test-title',
+    };
+    const testStyles = {
+      item: { background: 'red' },
+      itemIcon: { color: 'blue' },
+      itemTitle: { color: 'yellow' },
+    };
+    const { container } = render(
+      <Tree
+        treeData={data}
+        expandedKeys={['0-0', '0-0-1']}
+        styles={testStyles}
+        classNames={testClassNames}
+      />,
+    );
+    const item = container.querySelector(`.${testClassNames.item}`);
+    const icon = container.querySelector('.rc-tree-iconEle');
+    const title = container.querySelector('.rc-tree-title');
+
+    expect(icon).toHaveStyle(testStyles.itemIcon);
+    expect(icon).toHaveClass(testClassNames.itemIcon);
+    expect(title).toHaveStyle(testStyles.itemTitle);
+    expect(title).toHaveClass(testClassNames.itemTitle);
+    expect(item).toHaveStyle(testStyles.item);
+  });
 });

From a45cf7bc879a5b928a2e21313553f6cc8739aa7d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?=
 <smith3816@gmail.com>
Date: Wed, 19 Mar 2025 11:57:29 +0800
Subject: [PATCH 392/397] chore: bump version to 1.0.1

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index d5a37fdb..3f7c697b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@rc-component/tree",
-  "version": "1.0.0",
+  "version": "1.0.1",
   "description": "tree ui component for react",
   "engines": {
     "node": ">=10.x"

From c2bca8d2c84a2ba07c997faf75287426c24d5498 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 4 Jul 2025 10:25:54 +0800
Subject: [PATCH 393/397] chore(deps-dev): bump @types/jest from 29.5.14 to
 30.0.0 (#952)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 3f7c697b..c0cee074 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
     "@rc-component/father-plugin": "^2.0.3",
     "@testing-library/jest-dom": "^6.1.5",
     "@testing-library/react": "^16.1.0",
-    "@types/jest": "^29.5.10",
+    "@types/jest": "^30.0.0",
     "@types/node": "^22.7.3",
     "@types/react": "^19.0.1",
     "@types/react-dom": "^19.0.1",

From 3f1e3b41859e05d3c95191069dda4bb643bbd65b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 4 Jul 2025 10:26:01 +0800
Subject: [PATCH 394/397] chore(deps-dev): bump eslint-plugin-jest from 28.14.0
 to 29.0.1 (#954)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index c0cee074..64d2c46b 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
     "@umijs/fabric": "^4.0.1",
     "dumi": "^2.1.0",
     "eslint": "^8.55.0",
-    "eslint-plugin-jest": "^28.8.3",
+    "eslint-plugin-jest": "^29.0.1",
     "eslint-plugin-unicorn": "^56.0.1",
     "father": "^4.4.0",
     "gh-pages": "^6.1.1",

From 0cebb885eed45dee189e350bdb0f23429de8c5ee Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 4 Jul 2025 10:26:11 +0800
Subject: [PATCH 395/397] chore(deps-dev): bump lint-staged from 15.5.2 to
 16.1.2 (#951)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 64d2c46b..3a3d899c 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
     "glob": "^11.0.0",
     "husky": "^9.1.6",
     "less": "^4.2.1",
-    "lint-staged": "^15.2.10",
+    "lint-staged": "^16.1.2",
     "@rc-component/np": "^1.0.0",
     "prettier": "^3.3.3",
     "@rc-component/dialog": "^1.0.0",

From 395716c0925847365b71c792d119650d81b92c3a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 4 Jul 2025 10:31:39 +0800
Subject: [PATCH 396/397] chore(deps-dev): bump @types/node from 22.16.0 to
 24.0.10 (#960)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 3a3d899c..4ef54106 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,7 @@
     "@testing-library/jest-dom": "^6.1.5",
     "@testing-library/react": "^16.1.0",
     "@types/jest": "^30.0.0",
-    "@types/node": "^22.7.3",
+    "@types/node": "^24.0.10",
     "@types/react": "^19.0.1",
     "@types/react-dom": "^19.0.1",
     "@types/warning": "^3.0.0",

From 8d8d7915439e2235fe5c0592dbcb820085705463 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 4 Jul 2025 10:38:54 +0800
Subject: [PATCH 397/397] chore(deps-dev): bump @rc-component/father-plugin
 from 1.1.0 to 2.0.3 (#919)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: afc163 <afc163@gmail.com>
