@douyinfe/semi-ui 2.6.0 → 2.7.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/button/__test__/button.test.js +7 -0
  2. package/button/buttonGroup.tsx +5 -2
  3. package/cascader/__test__/cascader.test.js +159 -81
  4. package/cascader/_story/cascader.stories.js +36 -23
  5. package/cascader/index.tsx +26 -5
  6. package/datePicker/_story/v2/InsetInput.jsx +104 -0
  7. package/datePicker/_story/v2/InsetInputE2E.jsx +69 -0
  8. package/datePicker/_story/v2/index.js +2 -0
  9. package/datePicker/dateInput.tsx +95 -9
  10. package/datePicker/datePicker.tsx +89 -15
  11. package/datePicker/index.tsx +15 -0
  12. package/datePicker/insetInput.tsx +76 -0
  13. package/datePicker/monthsGrid.tsx +14 -7
  14. package/dist/css/semi.css +104 -2
  15. package/dist/css/semi.min.css +1 -1
  16. package/dist/umd/semi-ui.js +902 -147
  17. package/dist/umd/semi-ui.js.map +1 -1
  18. package/dist/umd/semi-ui.min.js +1 -1
  19. package/dist/umd/semi-ui.min.js.map +1 -1
  20. package/input/_story/input.stories.js +13 -0
  21. package/lib/cjs/button/buttonGroup.d.ts +1 -0
  22. package/lib/cjs/button/buttonGroup.js +6 -2
  23. package/lib/cjs/cascader/index.d.ts +1 -0
  24. package/lib/cjs/cascader/index.js +38 -9
  25. package/lib/cjs/datePicker/dateInput.d.ts +9 -2
  26. package/lib/cjs/datePicker/dateInput.js +92 -9
  27. package/lib/cjs/datePicker/datePicker.d.ts +7 -2
  28. package/lib/cjs/datePicker/datePicker.js +123 -18
  29. package/lib/cjs/datePicker/index.js +24 -2
  30. package/lib/cjs/datePicker/insetInput.d.ts +21 -0
  31. package/lib/cjs/datePicker/insetInput.js +80 -0
  32. package/lib/cjs/datePicker/monthsGrid.js +19 -7
  33. package/lib/cjs/tree/index.js +5 -3
  34. package/lib/cjs/tree/interface.d.ts +1 -0
  35. package/lib/cjs/tree/nodeList.js +2 -1
  36. package/lib/cjs/treeSelect/index.js +7 -3
  37. package/lib/es/button/buttonGroup.d.ts +1 -0
  38. package/lib/es/button/buttonGroup.js +5 -2
  39. package/lib/es/cascader/index.d.ts +1 -0
  40. package/lib/es/cascader/index.js +35 -6
  41. package/lib/es/datePicker/dateInput.d.ts +9 -2
  42. package/lib/es/datePicker/dateInput.js +91 -9
  43. package/lib/es/datePicker/datePicker.d.ts +7 -2
  44. package/lib/es/datePicker/datePicker.js +124 -18
  45. package/lib/es/datePicker/index.js +20 -0
  46. package/lib/es/datePicker/insetInput.d.ts +21 -0
  47. package/lib/es/datePicker/insetInput.js +65 -0
  48. package/lib/es/datePicker/monthsGrid.js +19 -7
  49. package/lib/es/tree/index.js +5 -3
  50. package/lib/es/tree/interface.d.ts +1 -0
  51. package/lib/es/tree/nodeList.js +2 -1
  52. package/lib/es/treeSelect/index.js +7 -3
  53. package/package.json +9 -9
  54. package/tree/__test__/tree.test.js +87 -2
  55. package/tree/_story/tree.stories.js +65 -5
  56. package/tree/index.tsx +4 -2
  57. package/tree/interface.ts +1 -0
  58. package/tree/nodeList.tsx +2 -2
  59. package/treeSelect/__test__/treeSelect.test.js +28 -0
  60. package/treeSelect/_story/treeSelect.stories.js +55 -2
  61. package/treeSelect/index.tsx +11 -3
@@ -0,0 +1,65 @@
1
+ import _get from "lodash/get";
2
+ import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/includes";
3
+ import React from 'react';
4
+ import Input from '../input';
5
+ export function InsetDateInput(props) {
6
+ const {
7
+ insetInputValue,
8
+ valuePath,
9
+ onFocus,
10
+ onChange,
11
+ placeholder,
12
+ forwardRef
13
+ } = props;
14
+
15
+ const value = _get(insetInputValue, valuePath);
16
+
17
+ return /*#__PURE__*/React.createElement(Input, {
18
+ value: value,
19
+ onChange: (value, event) => {
20
+ onChange({
21
+ value,
22
+ event,
23
+ insetInputValue,
24
+ valuePath
25
+ });
26
+ },
27
+ onFocus: onFocus,
28
+ placeholder: placeholder,
29
+ ref: forwardRef
30
+ });
31
+ }
32
+ export function InsetTimeInput(props) {
33
+ const {
34
+ insetInputValue,
35
+ valuePath,
36
+ type,
37
+ onFocus,
38
+ onChange,
39
+ placeholder,
40
+ disabled
41
+ } = props;
42
+
43
+ const _isTimeType = _includesInstanceProperty(type).call(type, 'Time');
44
+
45
+ if (!_isTimeType) {
46
+ return null;
47
+ }
48
+
49
+ const value = _get(insetInputValue, valuePath);
50
+
51
+ return /*#__PURE__*/React.createElement(Input, {
52
+ value: value,
53
+ onChange: (value, event) => {
54
+ onChange({
55
+ value,
56
+ event,
57
+ insetInputValue,
58
+ valuePath
59
+ });
60
+ },
61
+ onFocus: onFocus,
62
+ placeholder: placeholder,
63
+ disabled: disabled
64
+ });
65
+ }
@@ -9,6 +9,8 @@ import _includesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/ins
9
9
 
10
10
  /* eslint-disable jsx-a11y/interactive-supports-focus,jsx-a11y/click-events-have-key-events */
11
11
 
12
+ /* eslint-disable jsx-a11y/no-static-element-interactions */
13
+
12
14
  /* eslint-disable react/no-did-update-set-state */
13
15
 
14
16
  /* eslint-disable max-len */
@@ -284,6 +286,9 @@ export default class MonthsGrid extends BaseComponent {
284
286
  monthRight,
285
287
  currentPanelHeight
286
288
  } = this.state;
289
+ const {
290
+ insetInput
291
+ } = this.props;
287
292
  const panelDetail = panelType === strings.PANEL_TYPE_RIGHT ? monthRight : monthLeft;
288
293
  const {
289
294
  isTimePickerOpen,
@@ -306,7 +311,7 @@ export default class MonthsGrid extends BaseComponent {
306
311
  style.minWidth = wrap.getBoundingClientRect().width;
307
312
  }
308
313
 
309
- if (this.leftIsYearOrTime() && this.rightIsYearOrTime()) {
314
+ if (this.leftIsYearOrTime() && this.rightIsYearOrTime() && !insetInput) {
310
315
  /**
311
316
  * left和right同时为tpk时,panel会有一个minHeight
312
317
  * 如果缓存的currentPanelHeight为0,则需要计算滚动列表的高度
@@ -316,7 +321,7 @@ export default class MonthsGrid extends BaseComponent {
316
321
  * When left and right are tpk at the same time, the panel will have a minHeight
317
322
  * If the cached currentPanelHeight is 0, you need to calculate the height of the scrolling list
318
323
  * If there is a cached value, use currentPanelHeight (if this height is less than the actual value, it will affect the number of cycles in the ScrollList to render the list)
319
- * See packages/semi-foundation/scrollList/itemF oundation.js initWheelList function
324
+ * See packages/semi-foundation/scrollList/itemFoundation.js initWheelList function
320
325
  */
321
326
  style.minHeight = currentPanelHeight ? currentPanelHeight : this.calcScrollListHeight();
322
327
  }
@@ -324,10 +329,14 @@ export default class MonthsGrid extends BaseComponent {
324
329
  monthCls = classnames(monthCls, "".concat(prefixCls, "-yam-showing"));
325
330
  }
326
331
 
332
+ const _isDatePanelOpen = !(isYearPickerOpen || isTimePickerOpen);
333
+
334
+ const xOpenType = _isDatePanelOpen ? 'date' : isYearPickerOpen ? 'year' : 'time';
327
335
  return /*#__PURE__*/React.createElement("div", {
328
336
  className: monthCls,
329
337
  key: panelType,
330
- style: style
338
+ style: style,
339
+ "x-open-type": xOpenType
331
340
  }, yearAndMonthLayer, timePickerLayer, this.foundation.isRangeType() ? panelContent : isYearPickerOpen || isTimePickerOpen ? null : panelContent, this.renderSwitch(panelType));
332
341
  }
333
342
 
@@ -538,10 +547,11 @@ export default class MonthsGrid extends BaseComponent {
538
547
  locale,
539
548
  disabledTimePicker,
540
549
  density,
541
- dateFnsLocale
542
- } = this.props; // Type: date, dateRange, year, month, no rendering required
550
+ dateFnsLocale,
551
+ insetInput
552
+ } = this.props; // Type: date, dateRange, year, month, inset input no rendering required
543
553
 
544
- if (!_includesInstanceProperty(type).call(type, 'Time')) {
554
+ if (!_includesInstanceProperty(type).call(type, 'Time') || insetInput) {
545
555
  return null;
546
556
  } // switch year/month & time
547
557
 
@@ -614,7 +624,8 @@ export default class MonthsGrid extends BaseComponent {
614
624
  monthRight
615
625
  } = this.state;
616
626
  const {
617
- type
627
+ type,
628
+ insetInput
618
629
  } = this.props;
619
630
  const monthGridCls = classnames({
620
631
  ["".concat(prefixCls, "-month-grid")]: true
@@ -636,6 +647,7 @@ export default class MonthsGrid extends BaseComponent {
636
647
  className: monthGridCls,
637
648
  "x-type": type,
638
649
  "x-panel-yearandmonth-open-type": yearOpenType,
650
+ "x-insetInput": insetInput ? "true" : "false",
639
651
  ref: current => this.cacheRefCurrent('monthGrid', current)
640
652
  }, content);
641
653
  }
@@ -216,7 +216,8 @@ class Tree extends BaseComponent {
216
216
  const isSeaching = Boolean(props.filterTreeNode && prevState.inputValue && prevState.inputValue.length);
217
217
  const newState = {
218
218
  prevProps: props
219
- }; // Accept a props field as a parameter to determine whether to update the field
219
+ };
220
+ const isExpandControlled = ('expandedKeys' in props); // Accept a props field as a parameter to determine whether to update the field
220
221
 
221
222
  const needUpdate = name => {
222
223
  const firstInProps = !prevProps && name in props;
@@ -260,7 +261,8 @@ class Tree extends BaseComponent {
260
261
  }
261
262
  }
262
263
 
263
- const expandAllWhenDataChange = (needUpdate('treeDataSimpleJson') || needUpdate('treeData')) && props.expandAll;
264
+ const dataUpdated = needUpdate('treeDataSimpleJson') || needUpdate('treeData');
265
+ const expandAllWhenDataChange = dataUpdated && props.expandAll;
264
266
 
265
267
  if (!isSeaching) {
266
268
  // Update expandedKeys
@@ -286,7 +288,7 @@ class Tree extends BaseComponent {
286
288
  newState.expandedKeys = calcExpandedKeys(props.defaultExpandedKeys, keyEntities);
287
289
  } else if (!prevProps && props.defaultValue) {
288
290
  newState.expandedKeys = calcExpandedKeysForValues(props.defaultValue, keyEntities, props.multiple, valueEntities);
289
- } else if (!prevProps && props.value) {
291
+ } else if ((!prevProps || !isExpandControlled && dataUpdated) && props.value) {
290
292
  newState.expandedKeys = calcExpandedKeysForValues(props.value, keyEntities, props.multiple, valueEntities);
291
293
  }
292
294
 
@@ -109,6 +109,7 @@ export interface NodeListProps {
109
109
  motionKeys: Set<string>;
110
110
  motionType: string;
111
111
  flattenList: FlattenNode[] | undefined;
112
+ searchTargetIsDeep?: boolean;
112
113
  renderTreeNode: (treeNode: FlattenNode, ind?: number, style?: React.CSSProperties) => ReactNode;
113
114
  }
114
115
  export declare type TransitionNodes<T> = Array<T | Array<T>>;
@@ -83,12 +83,13 @@ export default class NodeList extends PureComponent {
83
83
  const {
84
84
  flattenNodes,
85
85
  motionType,
86
+ searchTargetIsDeep,
86
87
  renderTreeNode
87
88
  } = this.props;
88
89
  const {
89
90
  transitionNodes
90
91
  } = this.state;
91
- const mapData = transitionNodes.length ? transitionNodes : flattenNodes;
92
+ const mapData = transitionNodes.length && !searchTargetIsDeep ? transitionNodes : flattenNodes;
92
93
 
93
94
  const options = _mapInstanceProperty(mapData).call(mapData, treeNode => {
94
95
  const isMotionNode = _Array$isArray(treeNode);
@@ -741,7 +741,8 @@ class TreeSelect extends BaseComponent {
741
741
  const {
742
742
  flattenNodes,
743
743
  motionKeys,
744
- motionType
744
+ motionType,
745
+ filteredKeys
745
746
  } = this.state;
746
747
  const {
747
748
  direction
@@ -750,6 +751,7 @@ class TreeSelect extends BaseComponent {
750
751
  virtualize,
751
752
  motionExpand
752
753
  } = this.props;
754
+ const isExpandControlled = ('expandedKeys' in this.props);
753
755
 
754
756
  if (!virtualize || _isEmpty(virtualize)) {
755
757
  return /*#__PURE__*/React.createElement(NodeList, {
@@ -757,6 +759,8 @@ class TreeSelect extends BaseComponent {
757
759
  flattenList: this._flattenNodes,
758
760
  motionKeys: motionExpand ? motionKeys : new _Set([]),
759
761
  motionType: motionType,
762
+ // When motionKeys is empty, but filteredKeys is not empty (that is, the search hits), this situation should be distinguished from ordinary motionKeys
763
+ searchTargetIsDeep: isExpandControlled && motionExpand && _isEmpty(motionKeys) && !_isEmpty(filteredKeys),
760
764
  onMotionEnd: this.onMotionEnd,
761
765
  renderTreeNode: this.renderTreeNode
762
766
  });
@@ -1076,8 +1080,8 @@ class TreeSelect extends BaseComponent {
1076
1080
  notifySelect: (selectKey, bool, node) => {
1077
1081
  this.props.onSelect && this.props.onSelect(selectKey, bool, node);
1078
1082
  },
1079
- notifySearch: input => {
1080
- this.props.onSearch && this.props.onSearch(input);
1083
+ notifySearch: (input, filteredExpandedKeys) => {
1084
+ this.props.onSearch && this.props.onSearch(input, filteredExpandedKeys);
1081
1085
  },
1082
1086
  cacheFlattenNodes: bool => {
1083
1087
  this._flattenNodes = bool ? cloneDeep(this.state.flattenNodes) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douyinfe/semi-ui",
3
- "version": "2.6.0",
3
+ "version": "2.7.0-beta.0",
4
4
  "description": "",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/es/index.js",
@@ -14,12 +14,12 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@babel/runtime-corejs3": "^7.15.4",
17
- "@douyinfe/semi-animation": "2.6.0",
18
- "@douyinfe/semi-animation-react": "2.6.0",
19
- "@douyinfe/semi-foundation": "2.6.0",
20
- "@douyinfe/semi-icons": "2.6.0",
21
- "@douyinfe/semi-illustrations": "2.6.0",
22
- "@douyinfe/semi-theme-default": "2.6.0",
17
+ "@douyinfe/semi-animation": "2.7.0-beta.0",
18
+ "@douyinfe/semi-animation-react": "2.7.0-beta.0",
19
+ "@douyinfe/semi-foundation": "2.7.0-beta.0",
20
+ "@douyinfe/semi-icons": "2.7.0-beta.0",
21
+ "@douyinfe/semi-illustrations": "2.7.0-beta.0",
22
+ "@douyinfe/semi-theme-default": "2.7.0-beta.0",
23
23
  "@types/react-window": "^1.8.2",
24
24
  "async-validator": "^3.5.0",
25
25
  "classnames": "^2.2.6",
@@ -69,13 +69,13 @@
69
69
  ],
70
70
  "author": "",
71
71
  "license": "MIT",
72
- "gitHead": "d3a844137fb02b81a93320eb5c183636b05a4a68",
72
+ "gitHead": "51cc4f6fa52f3275728d2112a98446ba54619c5b",
73
73
  "devDependencies": {
74
74
  "@babel/plugin-proposal-decorators": "^7.15.8",
75
75
  "@babel/plugin-transform-runtime": "^7.15.8",
76
76
  "@babel/preset-env": "^7.15.8",
77
77
  "@babel/preset-react": "^7.14.5",
78
- "@douyinfe/semi-scss-compile": "2.6.0",
78
+ "@douyinfe/semi-scss-compile": "2.7.0-beta.0",
79
79
  "@storybook/addon-knobs": "^6.3.1",
80
80
  "@types/lodash": "^4.14.176",
81
81
  "babel-loader": "^8.2.2",
@@ -1,6 +1,7 @@
1
- import { Tree, Icon } from '../../index';
2
- import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
1
+ import { isEqual } from 'lodash';
3
2
  import { IconMapPin } from '@douyinfe/semi-icons';
3
+ import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
4
+ import { Tree, Button } from '../../index';
4
5
 
5
6
  const treeChildren = [
6
7
  {
@@ -672,6 +673,90 @@ describe('Tree', () => {
672
673
  expect(tree.state().selectedKeys.length).toEqual(0);
673
674
  });
674
675
 
676
+
677
+ /**
678
+ * Detect whether the expanded item will be expanded according to the value when the value
679
+ * or treeData is changed, when expandedKeys is not controlled
680
+ */
681
+ const treeJsonData1 = {
682
+ "Node0": {
683
+ "Child Node0-0": '0-0',
684
+ "Child Node0-1": '0-1',
685
+ },
686
+ "Node1": {
687
+ "Child Node1-0": '1-0',
688
+ "Child Node1-1": '1-1',
689
+ }
690
+ };
691
+ const treeJsonData2 = {
692
+ "Updated Node0": {
693
+ "Updated Child Node0-0": {
694
+ 'Updated Child Node0-0-0':'0-0'
695
+ },
696
+ "Updated Child Node0-1": '0-1',
697
+ },
698
+ "Updated Node1": {
699
+ "Updated Child Node1-0": '1-0',
700
+ "Updated Child Node1-1": '1-1',
701
+ }
702
+ };
703
+
704
+ it('expandedKeys when treeDataSimpleJson update', () => {
705
+ const tree = mount(
706
+ <Tree
707
+ value='0-0'
708
+ multiple
709
+ treeDataSimpleJson={treeJsonData1}
710
+ />,
711
+ {
712
+ attachTo: document.getElementById('container')
713
+ }
714
+ );
715
+ const treeDataButton = mount(
716
+ <Button
717
+ onClick={() => {
718
+ if (isEqual(tree.props().treeDataSimpleJson, treeJsonData1)) {
719
+ tree.setProps({ treeDataSimpleJson: treeJsonData2 });
720
+ tree.update();
721
+ } else {
722
+ tree.setProps({ treeDataSimpleJson: treeJsonData1 });
723
+ tree.update();
724
+ }
725
+ }}
726
+ >
727
+ update treeData
728
+ </Button>
729
+ );
730
+ expect(
731
+ tree
732
+ .find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-1`)
733
+ .at(0)
734
+ .hasClass(`${BASE_CLASS_PREFIX}-tree-option-collapsed`)
735
+ ).toEqual(false);
736
+ expect(
737
+ tree
738
+ .find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`)
739
+ .at(0)
740
+ .hasClass(`${BASE_CLASS_PREFIX}-tree-option-collapsed`)
741
+ ).toEqual(true);
742
+ expect(
743
+ tree
744
+ .find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-1`)
745
+ .at(1)
746
+ .hasClass(`${BASE_CLASS_PREFIX}-tree-option-collapsed`)
747
+ ).toEqual(true);
748
+ treeDataButton.simulate('click');
749
+ expect(
750
+ tree
751
+ .find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-2`)
752
+ .at(0)
753
+ .hasClass(`${BASE_CLASS_PREFIX}-tree-option-collapsed`)
754
+ ).toEqual(false);
755
+ treeDataButton.simulate('click');
756
+ tree.unmount();
757
+ treeDataButton.unmount();
758
+ });
759
+
675
760
  it('expandAction = false / default behavior', () => {
676
761
  const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => { } } }
677
762
  let spyOnExpand = sinon.spy(() => { });
@@ -1,12 +1,11 @@
1
1
  import React, { useRef, useState } from 'react';
2
+ import { cloneDeep, difference, isEqual } from 'lodash';
3
+ import { IconEdit, IconMapPin, IconMore } from '@douyinfe/semi-icons';
2
4
  import Tree from '../index';
3
5
  import AutoSizer from '../autoSizer';
4
- import { Button, ButtonGroup, Input, Popover, Toast } from '../../index';
6
+ import { Button, ButtonGroup, Input, Popover, Toast, Space } from '../../index';
5
7
  import BigTree from './BigData';
6
8
  import testData from './data';
7
- import { cloneDeep, difference, isEqual } from 'lodash';
8
- import { IconEdit, IconMapPin, IconMore } from '@douyinfe/semi-icons';
9
-
10
9
  const TreeNode = Tree.TreeNode;
11
10
 
12
11
  export default {
@@ -2338,4 +2337,65 @@ export const CheckRelationDemo = () => {
2338
2337
  />
2339
2338
  </>
2340
2339
  );
2341
- };
2340
+ };
2341
+
2342
+ export const ValueImpactExpansionWithDynamicTreeData = () => {
2343
+ const json = {
2344
+ "Node0": {
2345
+ "Child Node0-0": '0-0',
2346
+ "Child Node0-1": '0-1',
2347
+ },
2348
+ "Node1": {
2349
+ "Child Node1-0": '1-0',
2350
+ "Child Node1-1": '1-1',
2351
+ }
2352
+ }
2353
+ const json2 = {
2354
+ "Updated Node0": {
2355
+ "Updated Child Node0-0": {
2356
+ 'Updated Child Node0-0-0':'0-0'
2357
+ },
2358
+ "Updated Child Node0-1": '0-1',
2359
+ },
2360
+ "Updated Node1": {
2361
+ "Updated Child Node1-0": '1-0',
2362
+ "Updated Child Node1-1": '1-1',
2363
+ }
2364
+ }
2365
+ const style = {
2366
+ width: 260,
2367
+ height: 420,
2368
+ border: '1px solid var(--color-border)'
2369
+ }
2370
+ const [value, setValue] = useState('0-0')
2371
+ const [tree, setTree] = useState(json);
2372
+ const handleValueButtonClick = () => {
2373
+ if (value === '0-0') {
2374
+ setValue('1-0');
2375
+ } else {
2376
+ setValue('0-0');
2377
+ }
2378
+ }
2379
+ const handleTreeDataButtonClick = () => {
2380
+ if(isEqual(tree, json)){
2381
+ setTree(json2);
2382
+ } else {
2383
+ setTree(json);
2384
+ }
2385
+ }
2386
+ return (
2387
+ <>
2388
+ <div>value 受控时,当 treeData/treeDataSimpleJson 改变时,应该根据 value 自动展开</div>
2389
+ <Tree
2390
+ value={value}
2391
+ treeDataSimpleJson={tree}
2392
+ style={style}
2393
+ onChange={v => setValue(v)}
2394
+ />
2395
+ <Space>
2396
+ <Button onClick={handleValueButtonClick}>改变 value</Button>
2397
+ <Button onClick={handleTreeDataButtonClick}>改变 TreeData</Button>
2398
+ </Space>
2399
+ </>
2400
+ )
2401
+ }
package/tree/index.tsx CHANGED
@@ -192,6 +192,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
192
192
  const newState: Partial<TreeState> = {
193
193
  prevProps: props,
194
194
  };
195
+ const isExpandControlled = 'expandedKeys' in props;
195
196
 
196
197
  // Accept a props field as a parameter to determine whether to update the field
197
198
  const needUpdate = (name: string) => {
@@ -239,7 +240,8 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
239
240
  newState.motionType = null;
240
241
  }
241
242
  }
242
- const expandAllWhenDataChange = (needUpdate('treeDataSimpleJson') || needUpdate('treeData')) && props.expandAll;
243
+ const dataUpdated = needUpdate('treeDataSimpleJson') || needUpdate('treeData');
244
+ const expandAllWhenDataChange = dataUpdated && props.expandAll;
243
245
  if (!isSeaching) {
244
246
  // Update expandedKeys
245
247
  if (needUpdate('expandedKeys') || (prevProps && needUpdate('autoExpandParent'))) {
@@ -273,7 +275,7 @@ class Tree extends BaseComponent<TreeProps, TreeState> {
273
275
  props.multiple,
274
276
  valueEntities
275
277
  );
276
- } else if (!prevProps && props.value) {
278
+ } else if ((!prevProps || (!isExpandControlled && dataUpdated)) && props.value) {
277
279
  newState.expandedKeys = calcExpandedKeysForValues(
278
280
  props.value,
279
281
  keyEntities,
package/tree/interface.ts CHANGED
@@ -131,6 +131,7 @@ export interface NodeListProps {
131
131
  motionKeys: Set<string>;
132
132
  motionType: string;
133
133
  flattenList: FlattenNode[] | undefined;
134
+ searchTargetIsDeep?: boolean;
134
135
  renderTreeNode: (treeNode: FlattenNode, ind?: number, style?: React.CSSProperties) => ReactNode;
135
136
  }
136
137
  export type TransitionNodes<T> = Array<T | Array<T>>;
package/tree/nodeList.tsx CHANGED
@@ -59,9 +59,9 @@ export default class NodeList extends PureComponent<NodeListProps, NodeListState
59
59
  };
60
60
 
61
61
  render() {
62
- const { flattenNodes, motionType, renderTreeNode } = this.props;
62
+ const { flattenNodes, motionType, searchTargetIsDeep, renderTreeNode } = this.props;
63
63
  const { transitionNodes } = this.state;
64
- const mapData = transitionNodes.length ? transitionNodes : flattenNodes;
64
+ const mapData = transitionNodes.length && !searchTargetIsDeep ? transitionNodes : flattenNodes;
65
65
  const options = mapData.map(treeNode => {
66
66
  const isMotionNode = Array.isArray(treeNode);
67
67
  if (isMotionNode && !(treeNode as FlattenNode[]).length) {
@@ -552,6 +552,17 @@ describe('TreeSelect', () => {
552
552
  searchWrapper.find('input').simulate('change', event);
553
553
  expect(spyOnSearch.calledOnce).toBe(true);
554
554
  expect(spyOnSearch.calledWithMatch(searchValue)).toBe(true);
555
+
556
+ /* Check the input parameters of onSearch */
557
+ searchValue = '北京';
558
+ event = { target: { value: searchValue } };
559
+ searchWrapper.find('input').simulate('change', event);
560
+ expect(spyOnSearch.callCount).toBe(2);
561
+ const firstCall = spyOnSearch.getCall(1);
562
+ const args = firstCall.args;
563
+ expect(args[0]).toEqual('北京');
564
+ expect(args[1].includes('yazhou')).toEqual(true);
565
+ expect(args[1].includes('zhongguo')).toEqual(true);
555
566
  });
556
567
 
557
568
  it('filterTreeNode shows correct result', () => {
@@ -937,4 +948,21 @@ describe('TreeSelect', () => {
937
948
  ).toEqual(0);
938
949
  });
939
950
 
951
+ it('expandedKeys controlled + filterTreeNode', () => {
952
+ const spyOnExpand = sinon.spy(() => { });
953
+ const treeSelect = getTreeSelect({
954
+ expandedKeys: [],
955
+ onExpand: spyOnExpand,
956
+ filterTreeNode: true,
957
+ });
958
+ const searchWrapper = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-search-wrapper`);
959
+ const searchValue = '北京';
960
+ const event = { target: { value: searchValue } };
961
+ searchWrapper.find('input').simulate('change', event);
962
+ expect(spyOnExpand.callCount).toBe(0);
963
+ /* filter won't impact on the expansion of node when expandedKeys is controlled */
964
+ const topNode = treeSelect.find(`.${BASE_CLASS_PREFIX}-tree-option.${BASE_CLASS_PREFIX}-tree-option-level-1`);
965
+ expect(topNode.at(0).hasClass(`${BASE_CLASS_PREFIX}-tree-option-collapsed`)).toEqual(true);
966
+ expect(topNode.at(1).hasClass(`${BASE_CLASS_PREFIX}-tree-option-collapsed`)).toEqual(true);
967
+ });
940
968
  })
@@ -1,10 +1,11 @@
1
1
  import React, { useState } from 'react';
2
- import { Icon, Button, Form, Popover, Tag } from '../../index';
2
+ import { Icon, Button, Form, Popover, Tag, Typography } from '../../index';
3
3
  import TreeSelect from '../index';
4
4
  import { flattenDeep } from 'lodash';
5
5
  import CustomTrigger from './CustomTrigger';
6
6
  import { IconCreditCard } from '@douyinfe/semi-icons';
7
7
  const TreeNode = TreeSelect.TreeNode;
8
+ const { Title } = Typography;
8
9
 
9
10
  export default {
10
11
  title: 'TreeSelect',
@@ -1406,4 +1407,56 @@ export const CheckRelationDemo = () => {
1406
1407
  />
1407
1408
  </>
1408
1409
  );
1409
- };
1410
+ };
1411
+
1412
+ export const SearchableAndExpandedKeys = () => {
1413
+ const [expandedKeys1, setExpandedKeys1] = useState([]);
1414
+ const [expandedKeys2, setExpandedKeys2] = useState([]);
1415
+ const [expandedKeys3, setExpandedKeys3] = useState([]);
1416
+ return (
1417
+ <>
1418
+ <Title heading={6}>expandedKeys 受控</Title>
1419
+ <TreeSelect
1420
+ style={{ width: 300, marginBottom: 30 }}
1421
+ dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
1422
+ treeData={treeData2}
1423
+ expandedKeys={expandedKeys1}
1424
+ defaultValue='beijing'
1425
+ onExpand={v => {
1426
+ console.log('onExpand value: ', v);
1427
+ setExpandedKeys1(v);
1428
+ }}
1429
+ />
1430
+ <Title heading={6}>expandedKeys 受控 + 开启搜索</Title>
1431
+ <TreeSelect
1432
+ style={{ width: 300, marginBottom: 30 }}
1433
+ dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
1434
+ treeData={treeData2}
1435
+ filterTreeNode
1436
+ defaultValue='beijing'
1437
+ expandedKeys={expandedKeys2}
1438
+ onExpand={v => {
1439
+ console.log('onExpand value: ', v);
1440
+ setExpandedKeys2(v);
1441
+ }}
1442
+ />
1443
+ <Title heading={6}>expandedKeys 受控 + 开启搜索 + 搜索时更新 expandedKeys</Title>
1444
+ <TreeSelect
1445
+ style={{ width: 300, marginBottom: 30 }}
1446
+ dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
1447
+ treeData={treeData2}
1448
+ filterTreeNode
1449
+ expandedKeys={expandedKeys3}
1450
+ defaultValue='beijing'
1451
+ onExpand={v => {
1452
+ console.log('onExpand value: ', v);
1453
+ setExpandedKeys3(v)
1454
+ }}
1455
+ onSearch={(input, filterExpandedKeys) => {
1456
+ console.log('onExpand filterExpandedKeys: ', filterExpandedKeys);
1457
+ setExpandedKeys3(filterExpandedKeys);
1458
+ }}
1459
+ />
1460
+ </>
1461
+ )
1462
+ }
@@ -561,8 +561,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
561
561
  notifySelect: ((selectKey, bool, node) => {
562
562
  this.props.onSelect && this.props.onSelect(selectKey, bool, node);
563
563
  }),
564
- notifySearch: input => {
565
- this.props.onSearch && this.props.onSearch(input);
564
+ notifySearch: (input, filteredExpandedKeys) => {
565
+ this.props.onSearch && this.props.onSearch(input, filteredExpandedKeys);
566
566
  },
567
567
  cacheFlattenNodes: bool => {
568
568
  this._flattenNodes = bool ? cloneDeep(this.state.flattenNodes) : null;
@@ -1232,9 +1232,10 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
1232
1232
  };
1233
1233
 
1234
1234
  renderNodeList = () => {
1235
- const { flattenNodes, motionKeys, motionType } = this.state;
1235
+ const { flattenNodes, motionKeys, motionType, filteredKeys } = this.state;
1236
1236
  const { direction } = this.context;
1237
1237
  const { virtualize, motionExpand } = this.props;
1238
+ const isExpandControlled = 'expandedKeys' in this.props;
1238
1239
  if (!virtualize || isEmpty(virtualize)) {
1239
1240
  return (
1240
1241
  <NodeList
@@ -1242,6 +1243,13 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
1242
1243
  flattenList={this._flattenNodes}
1243
1244
  motionKeys={motionExpand ? motionKeys : new Set([])}
1244
1245
  motionType={motionType}
1246
+ // When motionKeys is empty, but filteredKeys is not empty (that is, the search hits), this situation should be distinguished from ordinary motionKeys
1247
+ searchTargetIsDeep={
1248
+ isExpandControlled &&
1249
+ motionExpand &&
1250
+ isEmpty(motionKeys) &&
1251
+ !isEmpty(filteredKeys)
1252
+ }
1245
1253
  onMotionEnd={this.onMotionEnd}
1246
1254
  renderTreeNode={this.renderTreeNode}
1247
1255
  />