Browse Source

fix: [TreeSelect] Fix TypeError in TreeSelect when checkRelation = un… (#1330)

* fix: [TreeSelect] Fix TypeError in TreeSelect when checkRelation = unRelated, value is not in treeData #1206

* fix: [TreeSelect] Fix TypeError in TreeSelect when checkRelation = unRelated, value is not in treeData #1206

* fix: [TreeSelect] Fix the problem that the value and defaultValue cannot be displayed in the trigger when the value and defaultValue are 0 in TreeSelect

* fix: [treeSelect] Fix the problem of displaying errors when the value type in TreeSelect is number

Co-authored-by: pointhalo <[email protected]>
YyumeiZhang 2 years ago
parent
commit
98b54debfc

+ 14 - 2
packages/semi-foundation/tree/treeUtil.ts

@@ -187,6 +187,9 @@ export function findKeysForValues(valueList: any, valueEntities: any, isMultiple
     valueList.forEach((val: string) => {
         if (val in valueEntities) {
             res.push(valueEntities[val]);
+        } else {
+            // if val not in valueEntities, then value push to keys array
+            val && res.push(val);
         }
     });
 
@@ -427,12 +430,17 @@ export function normalizedArr(val: any) {
     }
 }
 
-export function normalizeKeyList(keyList: any, keyEntities: KeyEntities, leafOnly = false) {
+// flag is used to determine whether to return when the key does not belong to the keys in keyEntities
+// export function normalizeKeyList(keyList: any, keyEntities: KeyEntities, leafOnly = false) {
+export function normalizeKeyList(keyList: any, keyEntities: KeyEntities, leafOnly = false, flag?: boolean) {
     const res: string[] = [];
     const keyListSet = new Set(keyList);
     if (!leafOnly) {
         keyList.forEach((key: string) => {
             if (!keyEntities[key]) {
+                if (flag) {
+                    res.push(key);
+                }
                 return;
             }
             const { parent } = keyEntities[key];
@@ -446,6 +454,10 @@ export function normalizeKeyList(keyList: any, keyEntities: KeyEntities, leafOnl
             if (keyEntities[key] && !isValid(keyEntities[key].children)) {
                 res.push(key);
             }
+            // when key is not in keyEntities, if flag is true, key should be push in res
+            if (!keyEntities[key] && flag) {
+                res.push(key);
+            }
         });
     }
     return res;
@@ -546,7 +558,7 @@ export function calcCheckedKeysForUnchecked(key: string, keyEntities: KeyEntitie
             calcCurrLevel(par);
         }
     };
-    calcCurrLevel(nodeItem);
+    nodeItem && calcCurrLevel(nodeItem);
     return {
         checkedKeys,
         halfCheckedKeys,

+ 38 - 6
packages/semi-foundation/treeSelect/foundation.ts

@@ -262,6 +262,36 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
         return Boolean(inputValue) && showFilteredOnly;
     }
 
+    findDataForValue(findValue: string) {
+        const { value, defaultValue } = this.getProps();
+        let valueArr = [];
+        if (value) {
+            valueArr = Array.isArray(value) ? value : [value];
+        } else if (defaultValue) {
+            valueArr = Array.isArray(defaultValue) ? defaultValue : [defaultValue];
+        }
+        return valueArr.find(item => {
+            return item.value === findValue || item.key === findValue;
+        });
+    }
+
+    constructDataForValue(value: string) {
+        const { treeNodeLabelProp } = this.getProps();
+        return {
+            key: value,
+            [treeNodeLabelProp]: value
+        };    
+    }
+
+    getDataForKeyNotInKeyEntities(value: string) {
+        const { onChangeWithObject } = this.getProps();
+        if (onChangeWithObject) {
+            return this.findDataForValue(value);
+        } else {
+            return this.constructDataForValue(value);
+        }
+    }
+
     getCopyFromState(items: string | string[]) {
         const res = {};
         normalizedArr(items).forEach(key => {
@@ -366,11 +396,11 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
         const { leafOnly, checkRelation } = this.getProps();
         let keyList = [];
         if (checkRelation === 'related') {
-            keyList = normalizeKeyList(key, keyEntities, leafOnly);
+            keyList = normalizeKeyList(key, keyEntities, leafOnly, true);
         } else if (checkRelation === 'unRelated') {
             keyList = key as string[];
         }
-        const nodes = keyList.map(i => keyEntities[i].data);
+        const nodes = keyList.map(key => (keyEntities[key] && keyEntities[key].data.key === key) ? keyEntities[key].data : this.getDataForKeyNotInKeyEntities(key));
         if (this.getProp('onChangeWithObject')) {
             this._adapter.notifyChangeWithObject(nodes, e);
         } else {
@@ -498,7 +528,7 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
     removeTag(eventKey: BasicTreeNodeData['key']) {
         const { disableStrictly, checkRelation } = this.getProps();
         const { keyEntities, disabledKeys, realCheckedKeys } = this.getStates();
-        const item = keyEntities[eventKey].data;
+        const item = (keyEntities[eventKey] && keyEntities[eventKey].data.key === eventKey) ? keyEntities[eventKey].data : this.getDataForKeyNotInKeyEntities(eventKey);
         if (item.disabled || (disableStrictly && disabledKeys.has(eventKey))) {
             return;
         }
@@ -789,9 +819,11 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
         const renderSelectedItem = isFunction(propRenderSelectedItem) ?
             propRenderSelectedItem :
             (item: BasicTreeNodeData) => get(item, treeNodeLabelProp, null);
-        const item = selectedKeys.length && keyEntities[selectedKeys[0]] ?
-            keyEntities[selectedKeys[0]].data :
-            undefined;
+        let item;
+        if (selectedKeys.length) {
+            const key = selectedKeys[0];
+            item = (keyEntities[key] && keyEntities[key].data.key === key) ? keyEntities[key].data : this.getDataForKeyNotInKeyEntities(key);
+        }
         const renderText = item && treeNodeLabelProp in item ? renderSelectedItem(item) : null;
         return renderText;
     }

+ 235 - 2
packages/semi-ui/treeSelect/__test__/treeMultiple.test.js

@@ -15,6 +15,19 @@ const treeChildren = [
     },
 ];
 
+const treeChildrenWithFakeObj = [
+    {
+        label: '北京',
+        value: 'Beijing',
+        key: 'beijing',
+    },
+    {
+        label: '鱼',
+        value: 'Fish',
+        key: 'fish',
+    },
+]
+
 const treeData = [
     {
         label: '亚洲',
@@ -103,6 +116,59 @@ const treeDataWithDisabled = [
     }
 ];
 
+const treeChildrenWithoutValue = [
+    {
+        label: '北京',
+        key: 'beijing',
+    },
+    {
+        label: '上海',
+        key: 'shanghai',
+    },
+];
+
+const treeDataWithoutValue = [
+    {
+        label: '亚洲',
+        key: 'yazhou',
+        children: [
+            {
+                label: '中国',
+                key: 'zhongguo',
+                children: treeChildrenWithoutValue,
+            },
+            {
+                label: '日本',
+                key: 'riben',
+                children: [
+                    {
+                        label: '东京',
+                        key: 'dongjing'
+                    },
+                    {
+                        label: '大阪',
+                        key: 'daban'
+                    }
+                ]
+            },
+        ],
+    },
+    {
+        label: '北美洲',
+        key: 'beimeizhou',
+        children: [
+            {
+                label: '美国',
+                key: 'meiguo'
+            },
+            {
+                label: '加拿大',
+                key: 'jianada'
+            }
+        ]
+    },
+];
+
 let commonProps = {
     motion: false,
     motionExpand: false,
@@ -597,7 +663,6 @@ describe('TreeSelect', () => {
                 defaultOpen
                 defaultExpandAll
                 disableStrictly
-                multiple
                 leafOnly
                 treeData={treeData}
                 {...commonProps}
@@ -616,7 +681,6 @@ describe('TreeSelect', () => {
                 defaultOpen
                 defaultExpandAll
                 disableStrictly
-                multiple
                 treeData={treeDataWithDisabled}
                 {...commonProps}
             />
@@ -772,4 +836,173 @@ describe('TreeSelect', () => {
         // onSelect first args is key, not value
         expect(spyOnSelect.calledWithMatch('zhongguo')).toEqual(true);
     });
+
+    it('option not in treeData + treeData item with value', () => {
+        const spyOnSelect = sinon.spy(() => { });
+        const spyOnChange = sinon.spy(() => { });
+        const treeSelect = getTreeSelect({
+            defaultValue: ['Beijing', 'fish'],
+            onSelect: spyOnSelect,
+            onChange: spyOnChange,
+        });
+
+        // Nodes that do not exist in treeData also appear in the tag input box
+        let tagGroup = treeSelect.find(`.${BASE_CLASS_PREFIX}-tag`);
+        expect(tagGroup.length).toEqual(2);
+        expect(tagGroup.at(0).instance().textContent).toEqual('北京');
+        expect(tagGroup.at(1).instance().textContent).toEqual('fish');
+
+        // Only one item is selected in the panel
+        let selectedNodes = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`);
+        let selectedNode = selectedNodes.at(0);
+        expect(selectedNode.find(`.${BASE_CLASS_PREFIX}-checkbox-inner-checked`).exists()).toEqual(true);
+        expect(selectedNode.find(`.${BASE_CLASS_PREFIX}-tree-option-label-text`).instance().textContent).toEqual('北京');
+       
+        // Check for fish in onSelect and onChange
+        let closeBtn = tagGroup.at(0).find(`.${BASE_CLASS_PREFIX}-tag-close`);
+        const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => { } } }
+        closeBtn.simulate('click', nativeEvent);
+        tagGroup = treeSelect.find(`.${BASE_CLASS_PREFIX}-tag`);
+        expect(tagGroup.length).toEqual(1);
+        expect(tagGroup.at(0).instance().textContent).toEqual('fish');
+        expect(spyOnChange.calledOnce).toBe(true);
+        expect(spyOnChange.calledWithMatch(['fish'])).toEqual(true);
+        
+        let nodeChina = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`).at(0);
+        // select China
+        nodeChina.simulate('click');
+        expect(spyOnSelect.calledWithMatch('zhongguo')).toEqual(true);
+        expect(spyOnChange.calledWithMatch(['fish', 'Zhongguo'])).toEqual(true);
+
+    });
+
+    it('option not in treeData + treeData item has value + onChangeWithObject', () => {
+        const spyOnSelect = sinon.spy(() => { });
+        const spyOnChange = sinon.spy(() => { });
+        const treeSelect = getTreeSelect({
+            defaultValue: treeChildrenWithFakeObj,
+            onSelect: spyOnSelect,
+            onChange: spyOnChange,
+            defaultExpandAll: true,
+            onChangeWithObject: true
+        });
+
+        // Nodes that do not exist in treeData also appear in the tag input box
+        let tagGroup = treeSelect.find(`.${BASE_CLASS_PREFIX}-tag`);
+        expect(tagGroup.length).toEqual(2);
+        expect(tagGroup.at(0).instance().textContent).toEqual('北京');
+        expect(tagGroup.at(1).instance().textContent).toEqual('鱼');
+
+        // Only one item is selected in the panel
+        let selectedNodes = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`);
+        let selectedNode = selectedNodes.at(0);
+        expect(selectedNode.find(`.${BASE_CLASS_PREFIX}-checkbox-inner-checked`).exists()).toEqual(true);
+        expect(selectedNode.find(`.${BASE_CLASS_PREFIX}-tree-option-label-text`).instance().textContent).toEqual('北京');
+    
+        // Check for fish in onSelect and onChange
+        let closeBtn = tagGroup.at(0).find(`.${BASE_CLASS_PREFIX}-tag-close`);
+        const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => { } } }
+        closeBtn.simulate('click', nativeEvent);
+        tagGroup = treeSelect.find(`.${BASE_CLASS_PREFIX}-tag`);
+        expect(tagGroup.length).toEqual(1);
+        expect(tagGroup.at(0).instance().textContent).toEqual('鱼');
+        expect(spyOnChange.calledOnce).toBe(true);
+        expect(spyOnChange.calledWithMatch([{ label: '鱼', value: 'Fish',  key: 'fish' }])).toEqual(true);
+        
+        let nodeChina = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`).at(0);
+        // select China
+        nodeChina.simulate('click');
+        expect(spyOnSelect.calledWithMatch('beijing')).toEqual(true);
+        expect(spyOnChange.calledWithMatch([
+            { label: '鱼', value: 'Fish',  key: 'fish' }, 
+            { label: '北京', value: 'Beijing', key: 'beijing' }
+        ])).toEqual(true);
+    });
+
+    it('option not in treeData + treeData item without value ', () => {
+        const spyOnSelect = sinon.spy(() => { });
+        const spyOnChange = sinon.spy(() => { });
+        const treeSelect = getTreeSelect({
+            treeData: treeDataWithoutValue,
+            defaultValue: ['beijing', 'fish'],
+            onSelect: spyOnSelect,
+            onChange: spyOnChange,
+        });
+
+        // Nodes that do not exist in treeData also appear in the tag input box
+        let tagGroup = treeSelect.find(`.${BASE_CLASS_PREFIX}-tag`);
+        expect(tagGroup.length).toEqual(2);
+        expect(tagGroup.at(0).instance().textContent).toEqual('北京');
+        expect(tagGroup.at(1).instance().textContent).toEqual('fish');
+
+        // Only one item is selected in the panel
+        let selectedNodes = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`);
+        let selectedNode = selectedNodes.at(0);
+        expect(selectedNode.find(`.${BASE_CLASS_PREFIX}-checkbox-inner-checked`).exists()).toEqual(true);
+        expect(selectedNode.find(`.${BASE_CLASS_PREFIX}-tree-option-label-text`).instance().textContent).toEqual('北京');
+       
+        // Check for fish in onSelect and onChange
+        let closeBtn = tagGroup.at(0).find(`.${BASE_CLASS_PREFIX}-tag-close`);
+        const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => { } } }
+        closeBtn.simulate('click', nativeEvent);
+        tagGroup = treeSelect.find(`.${BASE_CLASS_PREFIX}-tag`);
+        expect(tagGroup.length).toEqual(1);
+        expect(tagGroup.at(0).instance().textContent).toEqual('fish');
+        expect(spyOnChange.calledOnce).toBe(true);
+        expect(spyOnChange.calledWithMatch(['fish'])).toEqual(true);
+        
+        let nodeChina = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`).at(0);
+        // select China
+        nodeChina.simulate('click');
+        expect(spyOnSelect.calledWithMatch('zhongguo')).toEqual(true);
+        expect(spyOnChange.calledWithMatch(['fish', 'zhongguo'])).toEqual(true);
+
+    });
+
+    it('option not in treeData + treeData item without value + onChangeWithObject', () => {
+        const spyOnSelect = sinon.spy(() => { });
+        const spyOnChange = sinon.spy(() => { });
+        const treeSelect = getTreeSelect({
+            treeData: treeDataWithoutValue,
+            defaultValue: [
+                { label: '北京',  key: 'beijing' },
+                { label: '鱼', key: 'fish' }
+            ],
+            onSelect: spyOnSelect,
+            onChange: spyOnChange,
+            defaultExpandAll: true,
+            onChangeWithObject: true
+        });
+
+        // Nodes that do not exist in treeData also appear in the tag input box
+        let tagGroup = treeSelect.find(`.${BASE_CLASS_PREFIX}-tag`);
+        expect(tagGroup.length).toEqual(2);
+        expect(tagGroup.at(0).instance().textContent).toEqual('北京');
+        expect(tagGroup.at(1).instance().textContent).toEqual('鱼');
+
+        // Only one item is selected in the panel
+        let selectedNodes = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`);
+        let selectedNode = selectedNodes.at(0);
+        expect(selectedNode.find(`.${BASE_CLASS_PREFIX}-checkbox-inner-checked`).exists()).toEqual(true);
+        expect(selectedNode.find(`.${BASE_CLASS_PREFIX}-tree-option-label-text`).instance().textContent).toEqual('北京');
+    
+        // Check for fish in onSelect and onChange
+        let closeBtn = tagGroup.at(0).find(`.${BASE_CLASS_PREFIX}-tag-close`);
+        const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => { } } }
+        closeBtn.simulate('click', nativeEvent);
+        tagGroup = treeSelect.find(`.${BASE_CLASS_PREFIX}-tag`);
+        expect(tagGroup.length).toEqual(1);
+        expect(tagGroup.at(0).instance().textContent).toEqual('鱼');
+        expect(spyOnChange.calledOnce).toBe(true);
+        expect(spyOnChange.calledWithMatch([{ label: '鱼', key: 'fish' }])).toEqual(true);
+        
+        let nodeChina = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`).at(0);
+        // select China
+        nodeChina.simulate('click');
+        expect(spyOnSelect.calledWithMatch('beijing')).toEqual(true);
+        expect(spyOnChange.calledWithMatch([
+            { label: '鱼',  key: 'fish' }, 
+            { label: '北京', key: 'beijing' }
+        ])).toEqual(true);
+    });
 })

+ 121 - 2
packages/semi-ui/treeSelect/__test__/treeSelect.test.js

@@ -117,6 +117,59 @@ const treeData3 = [
     }
 ];
 
+const treeChildrenWithoutValue = [
+    {
+        label: '北京',
+        key: 'beijing',
+    },
+    {
+        label: '上海',
+        key: 'shanghai',
+    },
+];
+
+const treeDataWithoutValue = [
+    {
+        label: '亚洲',
+        key: 'yazhou',
+        children: [
+            {
+                label: '中国',
+                key: 'zhongguo',
+                children: treeChildrenWithoutValue,
+            },
+            {
+                label: '日本',
+                key: 'riben',
+                children: [
+                    {
+                        label: '东京',
+                        key: 'dongjing'
+                    },
+                    {
+                        label: '大阪',
+                        key: 'daban'
+                    }
+                ]
+            },
+        ],
+    },
+    {
+        label: '北美洲',
+        key: 'beimeizhou',
+        children: [
+            {
+                label: '美国',
+                key: 'meiguo'
+            },
+            {
+                label: '加拿大',
+                key: 'jianada'
+            }
+        ]
+    },
+];
+
 let commonProps = {
     motion: false,
     motionExpand: false,
@@ -905,12 +958,13 @@ describe('TreeSelect', () => {
         ).toEqual('北京');
         treeSelect.setProps({ treeData: treeData3});
         treeSelect.update();
+        // If the value exists, but not in the treeData, the value will be displayed in the trigger
         expect(
             treeSelect
             .find(`.${BASE_CLASS_PREFIX}-tree-select .${BASE_CLASS_PREFIX}-tree-select-selection span`)
             .getDOMNode()
             .textContent
-        ).toEqual('');
+        ).toEqual('Beijing');
     });
 
     it('treeData is updated should not clear value when controlled mode and multiple selection', () => {
@@ -945,7 +999,7 @@ describe('TreeSelect', () => {
             .at(0)
             .find(`.${BASE_CLASS_PREFIX}-tag-content`)
             .length
-        ).toEqual(0);
+        ).toEqual(1);
     });
 
     it('expandedKeys controlled + filterTreeNode', () => {
@@ -965,4 +1019,69 @@ describe('TreeSelect', () => {
         expect(topNode.at(0).hasClass(`${BASE_CLASS_PREFIX}-tree-option-collapsed`)).toEqual(true);
         expect(topNode.at(1).hasClass(`${BASE_CLASS_PREFIX}-tree-option-collapsed`)).toEqual(true);
     });
+    
+    it('option not in treeData + treeData item with value', () => {
+        const spyOnSelect = sinon.spy(() => { });
+        const spyOnChange = sinon.spy(() => { });
+        let treeSelect = getTreeSelect({
+            defaultValue: 'fish',
+            defaultExpandAll: true,
+            onSelect: spyOnSelect,
+            onChange: spyOnChange,
+        });
+        expect(treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-select-selection`).getDOMNode().textContent).toEqual('fish');
+        // beijing
+        let topNodeBeijing = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`).at(0);
+        topNodeBeijing.simulate('click');
+        expect(spyOnChange.calledWithMatch("Beijing")).toEqual(true);
+        treeSelect.unmount(); 
+
+        // onChangeWithObject
+        treeSelect = getTreeSelect({
+            defaultValue: { label: '鱼', value: 'Fish', key: 'fish' },
+            onChangeWithObject: true,
+            defaultExpandAll: true,
+            onSelect: spyOnSelect,
+            onChange: spyOnChange,
+        });
+        expect(treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-select-selection`).getDOMNode().textContent).toEqual('鱼');
+        // beijing
+        topNodeBeijing = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`).at(0);
+        topNodeBeijing.simulate('click');
+        expect(spyOnChange.calledWithMatch({ label: '北京', value: 'Beijing', key: 'beijing' })).toEqual(true);
+        treeSelect.unmount(); 
+    })
+
+    it('option not in treeData + treeData item without value', () => {
+        const spyOnSelect = sinon.spy(() => { });
+        const spyOnChange = sinon.spy(() => { });
+        let treeSelect = getTreeSelect({
+            treeData: treeDataWithoutValue,
+            defaultValue: 'fish',
+            defaultExpandAll: true,
+            onSelect: spyOnSelect,
+            onChange: spyOnChange,
+        });
+        expect(treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-select-selection`).getDOMNode().textContent).toEqual('fish');
+        // beijing
+        let topNodeBeijing = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`).at(0);
+        topNodeBeijing.simulate('click');
+        expect(spyOnChange.calledWithMatch("beijing")).toEqual(true);
+        treeSelect.unmount(); 
+
+        // onChangeWithObject
+        treeSelect = getTreeSelect({
+            defaultValue: { label: '鱼', key: 'fish' },
+            onChangeWithObject: true,
+            defaultExpandAll: true,
+            onSelect: spyOnSelect,
+            onChange: spyOnChange,
+        });
+        expect(treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-select-selection`).getDOMNode().textContent).toEqual('鱼');
+        // beijing
+        topNodeBeijing = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-3`).at(0);
+        topNodeBeijing.simulate('click');
+        expect(spyOnChange.calledWithMatch({ label: '北京', key: 'beijing' })).toEqual(true);
+        treeSelect.unmount(); 
+    })
 })

+ 298 - 1
packages/semi-ui/treeSelect/_story/treeSelect.stories.jsx

@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useMemo } from 'react';
 import { Icon, Button, Form, Popover, Tag, Typography, CheckboxGroup } from '../../index';
 import TreeSelect from '../index';
 import { flattenDeep } from 'lodash';
@@ -1745,3 +1745,300 @@ export const size = () => {
     <TreeSelect {...props} size={'large'} placeholder={'large'} />
   </>);
 }
+
+export const valueNotInTreeDataIssue = () => {
+  const treeData = [
+      {
+          key: "test",
+          label: "测试标签",
+          children: [
+              {
+                  key: "test_2",
+                  label: "测试二级标签"
+              },
+              {
+                  key: "jzr_test",
+                  label: "之睿测试"
+              }
+          ]
+      },
+  
+      {
+          key: "create",
+          label: "创作构思",
+          children: [
+              {
+                  key: "material",
+                  label: "素材积累"
+              },
+              {
+                  key: "lens_script",
+                  label: "分镜脚本"
+              }
+          ]
+      }
+  ];
+
+  const treeDataWithValue = [
+    {
+        value: "test",
+        key: "0",
+        label: "测试标签",
+        children: [
+            {
+                value: "test_2",
+                key: "0-1",
+                label: "测试二级标签"
+            },
+            {
+              value: "jzr_test",
+                key: "0-2",
+                label: "之睿测试"
+            }
+        ]
+    },
+
+    {
+        value: "create",
+        key: "1",
+        label: "创作构思",
+        children: [
+            {
+                value: "material",
+                key: "1-1",
+                label: "素材积累"
+            },
+            {
+                value: "lens_script",
+                key: "1-2",
+                label: "分镜脚本"
+            }
+        ]
+    }
+  ];
+
+  const commonProps = useMemo(() => {
+    return {
+      multiple: true,
+      style: { width: 300 },
+      dropdownStyle: { maxHeight: 400, overflow: 'auto' },
+      onChange: (value) => {
+        console.log('onChange', value);
+      },
+      onSelect: (value) => {
+        console.log('onSelect', value); 
+      },
+    };
+  }, []);
+  
+  return (
+    <>
+      <p style={{ backgroundColor: 'yellowgreen', width: 'fit-content' }}>多选,无 value</p>
+      <p>checkRelation='related'</p>
+        <TreeSelect
+          defaultExpandAll
+          defaultValue={["test_2", 0]}
+          treeData={treeData}
+          {...commonProps}
+        />
+        <p>checkRelation='unRelated'</p>
+        <TreeSelect
+          defaultExpandAll
+          defaultValue={["test_2", "fish"]}
+          checkRelation='unRelated'
+          treeData={treeData}
+          {...commonProps}
+        />
+        <p>onChangeWithObject, checkRelation='related'</p>
+        <TreeSelect
+          defaultExpandAll
+          onChangeWithObject
+          defaultValue={[
+            {
+              key: "test_2",
+              label: "测试二级标签"
+            },
+            {
+              key: "fish",
+              label: "鱼"
+            }
+          ]}
+          treeData={treeData}
+          {...commonProps}
+        />
+        <p>onChangeWithObject, checkRelation='unRelated'</p>
+        <TreeSelect
+          defaultExpandAll
+          onChangeWithObject
+          defaultValue={[
+            {
+              key: "test_2",
+              label: "测试二级标签"
+            },
+            {
+              key: "fish",
+              label: "鱼"
+            }
+          ]}
+          treeData={treeData}
+          {...commonProps}
+        />
+        <p style={{ backgroundColor: 'yellowgreen', width: 'fit-content' }}>多选,有 value</p>
+        <p>checkRelation='related'</p>
+        <TreeSelect
+          defaultExpandAll
+          defaultValue={["test", "fish"]}
+          treeData={treeDataWithValue}
+          {...commonProps}
+        />
+        <p>checkRelation='unRelated'</p>
+        <TreeSelect
+          defaultExpandAll
+          defaultValue={["test", "fish"]}
+          checkRelation='unRelated'
+          treeData={treeDataWithValue}
+          {...commonProps}
+        />
+        <p>onChangeWithObject, checkRelation='unRelated'</p>
+        <TreeSelect
+          defaultExpandAll
+          onChangeWithObject
+          defaultValue={[
+            {
+              value: "test_2",
+              key: "0-1",
+              label: "测试二级标签"
+            },
+            {
+              key: "fish",
+              value: "Fish",
+              label: "鱼"
+            }
+          ]}
+          treeData={treeDataWithValue}
+          {...commonProps}
+        />
+        <p>onChangeWithObject, checkRelation='unRelated'</p>
+        <TreeSelect
+          defaultExpandAll
+          onChangeWithObject
+          defaultValue={[
+            {
+              value: "test_2",
+              key: "0-1",
+              label: "测试二级标签"
+            },
+            {
+              key: "fish",
+              value: "Fish",
+              label: "鱼"
+            }
+          ]}
+          treeData={treeDataWithValue}
+          {...commonProps}
+        />
+        <p style={{ backgroundColor: 'yellowgreen', width: 'fit-content' }}>单选,无 value</p>
+        <TreeSelect
+          defaultExpandAll
+          defaultValue={"fish"}
+          treeData={treeData}
+          {...commonProps}
+          multiple={false}
+        />
+        <p>onChangeWithObject</p>
+        <TreeSelect
+          defaultExpandAll
+          defaultValue={{
+            key: "fish",
+            value: "Fish",
+            label: "鱼"
+          }}
+          treeData={treeData}
+          {...commonProps}
+          multiple={false}
+          onChangeWithObject
+        />
+        <p style={{ backgroundColor: 'yellowgreen', width: 'fit-content' }}>单选,有 value</p>
+        <TreeSelect
+          defaultExpandAll
+          defaultValue={"fish"}
+          treeData={treeDataWithValue}
+          {...commonProps}
+          multiple={false}
+        />
+        <p>onChangeWithObject</p>
+        <TreeSelect
+          defaultExpandAll
+          defaultValue={{
+            key: "fish",
+            value: "Fish",
+            label: "鱼"
+          }}
+          treeData={treeDataWithValue}
+          {...commonProps}
+          multiple={false}
+          onChangeWithObject
+        />
+    </>
+  );
+};
+
+class ValueTypeIsNumber extends React.Component {
+  constructor() {
+      super();
+      this.state = {
+          value: 1
+      };
+  }
+  onChange(value) {
+      console.log('onChange', value);
+      this.setState({ value });
+  }
+  render() {
+      const treeData = [
+           {
+              label: '北美洲',
+              value: 'North America',
+              key: '1',
+          },
+          {
+              label: '亚洲',
+              value: 'Asia',
+              key: '0',
+              children: [
+                  {
+                      label: '中国',
+                      value: 'China',
+                      key: '0-0',
+                      children: [
+                          {
+                              label: '北京',
+                              value: 'Beijing',
+                              key: '0-0-0',
+                          },
+                          {
+                              label: '上海',
+                              value: 'Shanghai',
+                              key: '0-0-1',
+                          },
+                      ],
+                  },
+              ],
+          },
+         
+      ];
+      return (
+          <TreeSelect
+              style={{ width: 300 }}
+              dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+              treeData={treeData}
+              value={this.state.value}
+              placeholder="请选择"
+              multiple
+              onChange={e => this.onChange(e)}
+          />
+      );
+  }
+}
+
+export const valueIsNumber = () => <ValueTypeIsNumber />

+ 18 - 14
packages/semi-ui/treeSelect/index.tsx

@@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
 import ReactDOM from 'react-dom';
 import cls from 'classnames';
 import PropTypes from 'prop-types';
-import { isEqual, isString, isEmpty, noop, get, isFunction } from 'lodash';
+import { isEqual, isString, isEmpty, noop, get, isFunction, isUndefined, isNull } from 'lodash';
 import TreeSelectFoundation, {
     Size,
     BasicTriggerRenderProps,
@@ -186,7 +186,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         arrowIcon: PropTypes.node,
         clearIcon: PropTypes.node,
         defaultOpen: PropTypes.bool,
-        defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
+        defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]),
         defaultExpandAll: PropTypes.bool,
         defaultExpandedKeys: PropTypes.array,
         expandAll: PropTypes.bool,
@@ -716,6 +716,10 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         this.foundation.handleClick(e);
     };
 
+    getDataForKeyNotInKeyEntities = (key: string) => {
+        return this.foundation.getDataForKeyNotInKeyEntities(key);
+    }
+
     /* istanbul ignore next */
     handleSelectionEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
         this.foundation.handleSelectionEnterPress(e);
@@ -764,14 +768,14 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             });
         let renderKeys = [];
         if (checkRelation === 'related') {
-            renderKeys = normalizeKeyList([...checkedKeys], keyEntities, leafOnly);
+            renderKeys = normalizeKeyList([...checkedKeys], keyEntities, leafOnly, true);
         } else if (checkRelation === 'unRelated' && Object.keys(keyEntities).length > 0) {
             renderKeys = [...realCheckedKeys];
         }
         const tagList: Array<React.ReactNode> = [];
         // eslint-disable-next-line @typescript-eslint/no-shadow
-        renderKeys.forEach((key: TreeNodeData['key']) => {
-            const item = keyEntities[key].data;
+        renderKeys.forEach((key: TreeNodeData['key'], index) => {
+            const item = (keyEntities[key] && keyEntities[key].data.key === key) ? keyEntities[key].data : this.getDataForKeyNotInKeyEntities(key);
             const onClose = (tagContent: any, e: React.MouseEvent) => {
                 if (e && typeof e.preventDefault === 'function') {
                     // make sure that tag will not hidden immediately in controlled mode
@@ -779,10 +783,10 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
                 }
                 this.removeTag(key);
             };
-            const { content, isRenderInTag } = (treeNodeLabelProp in item && item) ?
-                (renderSelectedItem as RenderSelectedItemInMultiple)(item, { index: key, onClose }) :
+            const { content, isRenderInTag } = (item && treeNodeLabelProp in item) ?
+                (renderSelectedItem as RenderSelectedItemInMultiple)(item, { index, onClose }) :
                 null;
-            if (!content) {
+            if (isNull(content) || isUndefined(content)) {
                 return;
             }
             const isDisabled = disabled || item.disabled || (disableStrictly && disabledKeys.has(item.key));
@@ -791,7 +795,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
                 color: 'white',
                 visible: true,
                 onClose,
-                key,
+                key: `tag-${key}-${index}`,
                 size: size === 'small' ? 'small' : 'large'
             };
             if (isRenderInTag) {
@@ -990,7 +994,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
                 },
                 className
             );
-        const triggerRenderKeys = multiple ? normalizeKeyList([...checkedKeys], keyEntities, leafOnly) : selectedKeys;
+        const triggerRenderKeys = multiple ? normalizeKeyList([...checkedKeys], keyEntities, leafOnly, true) : selectedKeys;
         const inner = useCustomTrigger ? (
             <Trigger
                 inputValue={inputValue}
@@ -1061,8 +1065,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             renderSelectedItem: propRenderSelectedItem,
             treeNodeLabelProp
         } = this.props;
-        const keyList = normalizeKeyList([key], keyEntities, leafOnly);
-        const nodes = keyList.map(i => keyEntities[i].data);
+        const keyList = normalizeKeyList([key], keyEntities, leafOnly, true);
+        const nodes = keyList.map(i => (keyEntities[key] && keyEntities[key].data.key === key) ? keyEntities[key].data : this.getDataForKeyNotInKeyEntities(key));
         const value = getValueOrKey(nodes);
         const tagCls = cls(`${prefixcls}-selection-tag`, {
             [`${prefixcls}-selection-tag-disabled`]: disabled,
@@ -1090,7 +1094,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
                 content: get(selectedItem, treeNodeLabelProp, null)
             });
         if (isFunction(renderSelectedItem)) {
-            const { content, isRenderInTag } = treeNodeLabelProp in item && item ?
+            const { content, isRenderInTag } = item && treeNodeLabelProp in item ?
                 (renderSelectedItem as RenderSelectedItemInMultiple)(item, { index: idx, onClose }) :
                 null;
             if (isRenderInTag) {
@@ -1128,7 +1132,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         } = this.state;
         let keyList = [];
         if (checkRelation === 'related') {
-            keyList = normalizeKeyList(checkedKeys, keyEntities, leafOnly);
+            keyList = normalizeKeyList(checkedKeys, keyEntities, leafOnly, true);
         } else if (checkRelation === 'unRelated') {
             keyList = [...realCheckedKeys];
         }