@douyinfe/semi-ui 2.6.0 → 2.7.1

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 (94) hide show
  1. package/_base/_story/index.stories.js +2 -6
  2. package/_portal/_story/portal.stories.js +1 -5
  3. package/_utils/hooks/usePrevFocus.ts +1 -0
  4. package/button/__test__/button.test.js +7 -0
  5. package/button/buttonGroup.tsx +5 -2
  6. package/cascader/__test__/cascader.test.js +159 -81
  7. package/cascader/_story/cascader.stories.js +36 -23
  8. package/cascader/index.tsx +26 -5
  9. package/datePicker/_story/v2/InsetInput.jsx +104 -0
  10. package/datePicker/_story/v2/InsetInputE2E.jsx +69 -0
  11. package/datePicker/_story/v2/index.js +2 -0
  12. package/datePicker/dateInput.tsx +95 -9
  13. package/datePicker/datePicker.tsx +89 -15
  14. package/datePicker/index.tsx +15 -0
  15. package/datePicker/insetInput.tsx +76 -0
  16. package/datePicker/monthsGrid.tsx +14 -7
  17. package/dist/css/semi.css +109 -7
  18. package/dist/css/semi.min.css +1 -1
  19. package/dist/umd/semi-ui.js +925 -152
  20. package/dist/umd/semi-ui.js.map +1 -1
  21. package/dist/umd/semi-ui.min.js +1 -1
  22. package/dist/umd/semi-ui.min.js.map +1 -1
  23. package/form/hooks/useFormApi.tsx +3 -2
  24. package/input/_story/input.stories.js +23 -1
  25. package/lib/cjs/_utils/hooks/usePrevFocus.js +1 -0
  26. package/lib/cjs/button/buttonGroup.d.ts +1 -0
  27. package/lib/cjs/button/buttonGroup.js +6 -2
  28. package/lib/cjs/cascader/index.d.ts +1 -0
  29. package/lib/cjs/cascader/index.js +38 -9
  30. package/lib/cjs/datePicker/dateInput.d.ts +9 -2
  31. package/lib/cjs/datePicker/dateInput.js +92 -9
  32. package/lib/cjs/datePicker/datePicker.d.ts +7 -2
  33. package/lib/cjs/datePicker/datePicker.js +123 -18
  34. package/lib/cjs/datePicker/index.js +24 -2
  35. package/lib/cjs/datePicker/insetInput.d.ts +21 -0
  36. package/lib/cjs/datePicker/insetInput.js +80 -0
  37. package/lib/cjs/datePicker/monthsGrid.js +19 -7
  38. package/lib/cjs/form/hooks/useFormApi.d.ts +2 -1
  39. package/lib/cjs/modal/useModal/HookModal.js +2 -0
  40. package/lib/cjs/radio/radioGroup.js +6 -0
  41. package/lib/cjs/select/index.js +5 -2
  42. package/lib/cjs/tag/group.d.ts +2 -0
  43. package/lib/cjs/tag/group.js +4 -2
  44. package/lib/cjs/tooltip/index.js +1 -1
  45. package/lib/cjs/tree/index.js +5 -3
  46. package/lib/cjs/tree/interface.d.ts +1 -0
  47. package/lib/cjs/tree/nodeList.js +3 -1
  48. package/lib/cjs/treeSelect/index.js +11 -3
  49. package/lib/es/_utils/hooks/usePrevFocus.js +2 -0
  50. package/lib/es/button/buttonGroup.d.ts +1 -0
  51. package/lib/es/button/buttonGroup.js +5 -2
  52. package/lib/es/cascader/index.d.ts +1 -0
  53. package/lib/es/cascader/index.js +35 -6
  54. package/lib/es/datePicker/dateInput.d.ts +9 -2
  55. package/lib/es/datePicker/dateInput.js +91 -9
  56. package/lib/es/datePicker/datePicker.d.ts +7 -2
  57. package/lib/es/datePicker/datePicker.js +124 -18
  58. package/lib/es/datePicker/index.js +20 -0
  59. package/lib/es/datePicker/insetInput.d.ts +21 -0
  60. package/lib/es/datePicker/insetInput.js +65 -0
  61. package/lib/es/datePicker/monthsGrid.js +19 -7
  62. package/lib/es/form/hooks/useFormApi.d.ts +2 -1
  63. package/lib/es/modal/useModal/HookModal.js +2 -0
  64. package/lib/es/radio/radioGroup.js +6 -0
  65. package/lib/es/select/index.js +5 -2
  66. package/lib/es/tag/group.d.ts +2 -0
  67. package/lib/es/tag/group.js +4 -2
  68. package/lib/es/tooltip/index.js +1 -1
  69. package/lib/es/tree/index.js +5 -3
  70. package/lib/es/tree/interface.d.ts +1 -0
  71. package/lib/es/tree/nodeList.js +3 -1
  72. package/lib/es/treeSelect/index.js +11 -3
  73. package/modal/_story/modal.stories.js +93 -1
  74. package/modal/useModal/HookModal.tsx +1 -0
  75. package/notification/_story/useNotification/index.jsx +21 -7
  76. package/package.json +9 -9
  77. package/radio/__test__/radioGroup.test.jsx +9 -1
  78. package/radio/_story/radio.stories.js +22 -1
  79. package/radio/radioGroup.tsx +9 -0
  80. package/select/_story/select.stories.js +73 -2
  81. package/select/index.tsx +5 -3
  82. package/table/_story/v2/FixedMemoryLeak/index.jsx +33 -0
  83. package/table/_story/v2/index.js +2 -1
  84. package/tag/group.tsx +5 -3
  85. package/toast/_story/toast.stories.js +41 -0
  86. package/tooltip/index.tsx +1 -1
  87. package/tree/__test__/tree.test.js +87 -2
  88. package/tree/_story/tree.stories.js +65 -5
  89. package/tree/index.tsx +4 -2
  90. package/tree/interface.ts +1 -0
  91. package/tree/nodeList.tsx +3 -2
  92. package/treeSelect/__test__/treeSelect.test.js +28 -0
  93. package/treeSelect/_story/treeSelect.stories.js +55 -2
  94. package/treeSelect/index.tsx +14 -3
@@ -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
@@ -55,13 +55,14 @@ export default class NodeList extends PureComponent<NodeListProps, NodeListState
55
55
  }
56
56
 
57
57
  onMotionEnd = () => {
58
+ typeof this.props.onMotionEnd === 'function' && this.props.onMotionEnd();
58
59
  this.setState({ transitionNodes: [] });
59
60
  };
60
61
 
61
62
  render() {
62
- const { flattenNodes, motionType, renderTreeNode } = this.props;
63
+ const { flattenNodes, motionType, searchTargetIsDeep, renderTreeNode } = this.props;
63
64
  const { transitionNodes } = this.state;
64
- const mapData = transitionNodes.length ? transitionNodes : flattenNodes;
65
+ const mapData = transitionNodes.length && !searchTargetIsDeep ? transitionNodes : flattenNodes;
65
66
  const options = mapData.map(treeNode => {
66
67
  const isMotionNode = Array.isArray(treeNode);
67
68
  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
+ }
@@ -331,6 +331,9 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
331
331
  this.clickOutsideHandler = null;
332
332
  this.foundation = new TreeSelectFoundation(this.adapter);
333
333
  this.treeSelectID = Math.random().toString(36).slice(2);
334
+ this.onMotionEnd = () => {
335
+ this.adapter.rePositionDropdown();
336
+ };
334
337
  }
335
338
 
336
339
  // eslint-disable-next-line max-lines-per-function
@@ -561,8 +564,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
561
564
  notifySelect: ((selectKey, bool, node) => {
562
565
  this.props.onSelect && this.props.onSelect(selectKey, bool, node);
563
566
  }),
564
- notifySearch: input => {
565
- this.props.onSearch && this.props.onSearch(input);
567
+ notifySearch: (input, filteredExpandedKeys) => {
568
+ this.props.onSearch && this.props.onSearch(input, filteredExpandedKeys);
566
569
  },
567
570
  cacheFlattenNodes: bool => {
568
571
  this._flattenNodes = bool ? cloneDeep(this.state.flattenNodes) : null;
@@ -1232,9 +1235,10 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
1232
1235
  };
1233
1236
 
1234
1237
  renderNodeList = () => {
1235
- const { flattenNodes, motionKeys, motionType } = this.state;
1238
+ const { flattenNodes, motionKeys, motionType, filteredKeys } = this.state;
1236
1239
  const { direction } = this.context;
1237
1240
  const { virtualize, motionExpand } = this.props;
1241
+ const isExpandControlled = 'expandedKeys' in this.props;
1238
1242
  if (!virtualize || isEmpty(virtualize)) {
1239
1243
  return (
1240
1244
  <NodeList
@@ -1242,6 +1246,13 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
1242
1246
  flattenList={this._flattenNodes}
1243
1247
  motionKeys={motionExpand ? motionKeys : new Set([])}
1244
1248
  motionType={motionType}
1249
+ // When motionKeys is empty, but filteredKeys is not empty (that is, the search hits), this situation should be distinguished from ordinary motionKeys
1250
+ searchTargetIsDeep={
1251
+ isExpandControlled &&
1252
+ motionExpand &&
1253
+ isEmpty(motionKeys) &&
1254
+ !isEmpty(filteredKeys)
1255
+ }
1245
1256
  onMotionEnd={this.onMotionEnd}
1246
1257
  renderTreeNode={this.renderTreeNode}
1247
1258
  />