@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
@@ -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
  });
@@ -897,6 +901,10 @@ class TreeSelect extends BaseComponent {
897
901
  this.clickOutsideHandler = null;
898
902
  this.foundation = new TreeSelectFoundation(this.adapter);
899
903
  this.treeSelectID = _sliceInstanceProperty(_context2 = Math.random().toString(36)).call(_context2, 2);
904
+
905
+ this.onMotionEnd = () => {
906
+ this.adapter.rePositionDropdown();
907
+ };
900
908
  } // eslint-disable-next-line max-lines-per-function
901
909
 
902
910
 
@@ -1076,8 +1084,8 @@ class TreeSelect extends BaseComponent {
1076
1084
  notifySelect: (selectKey, bool, node) => {
1077
1085
  this.props.onSelect && this.props.onSelect(selectKey, bool, node);
1078
1086
  },
1079
- notifySearch: input => {
1080
- this.props.onSearch && this.props.onSearch(input);
1087
+ notifySearch: (input, filteredExpandedKeys) => {
1088
+ this.props.onSearch && this.props.onSearch(input, filteredExpandedKeys);
1081
1089
  },
1082
1090
  cacheFlattenNodes: bool => {
1083
1091
  this._flattenNodes = bool ? cloneDeep(this.state.flattenNodes) : null;
@@ -1,5 +1,7 @@
1
1
  import React, { useState } from 'react';
2
- import { Select, Modal, Button, Tooltip, Popover } from '../../index';
2
+ import en_GB from '../../locale/source/en_GB';
3
+
4
+ import { Select, Modal, Button, Tooltip, Popover, ConfigProvider, Tag, Space } from '../../index';
3
5
  import CollapsibleInModal from './CollapsibleInModal';
4
6
  import DynamicContextDemo from './DynamicContext';
5
7
 
@@ -248,4 +250,94 @@ KeepDomNotLazy.story = {
248
250
  name: 'keepDOM && not lazy',
249
251
  };
250
252
 
253
+ export const UseModalDemo = () => {
254
+ const [modal, contextHolder] = Modal.useModal();
255
+ const config = { 'title': 'old title', 'content': 'old content' };
256
+
257
+ return (
258
+ <ConfigProvider locale={en_GB}>
259
+ <div>
260
+ <Button
261
+ onClick={() => {
262
+ const currentModal = modal.confirm(config);
263
+
264
+ setTimeout(() => {
265
+ currentModal.update({ title: "new title", content: "new content" });
266
+ }, 1000);
267
+ }}
268
+ >
269
+ Confirm Modal
270
+ </Button>
271
+ </div>
272
+ {contextHolder}
273
+ </ConfigProvider>
274
+ );
275
+ };
276
+ UseModalDemo.storyName = "useModal";
277
+
278
+ export const UseModalDestroy = () => {
279
+ const [modal, contextHolder] = Modal.useModal();
280
+ const config = { 'title': 'old title', 'content': 'old content' };
281
+
282
+ return (
283
+ <ConfigProvider locale={en_GB}>
284
+ <div>
285
+ <Button
286
+ onClick={() => {
287
+ const currentModal = modal.confirm(config);
288
+
289
+ setTimeout(() => {
290
+ currentModal.destroy();
291
+ }, 1000);
292
+ }}
293
+ >
294
+ Confirm Modal
295
+ </Button>
296
+ </div>
297
+ {contextHolder}
298
+ </ConfigProvider>
299
+ );
300
+ };
301
+ UseModalDestroy.storyName = "useModal destroy";
302
+
303
+ export const UseModalAfterClose = () => {
304
+ const [modal, contextHolder] = Modal.useModal();
305
+ const [closed, setClosed] = React.useState(false);
306
+ const [leave, setLeave] = React.useState(false);
307
+
308
+ const config = {
309
+ title: 'old title',
310
+ content: 'old content',
311
+ afterClose: () => {
312
+ setClosed(true);
313
+ },
314
+ motion: {
315
+ didLeave: () => {
316
+ console.log('didLeave');
317
+ setLeave(true);
318
+ }
319
+ }
320
+ };
251
321
 
322
+ return (
323
+ <ConfigProvider locale={en_GB}>
324
+ <Space>
325
+ <Button
326
+ onClick={() => {
327
+ const currentModal = modal.confirm(config);
328
+
329
+ setTimeout(() => {
330
+ currentModal.destroy();
331
+ }, 0);
332
+ }}
333
+ >
334
+ Confirm Modal
335
+ </Button>
336
+ <Tag>{`closed: ${closed}`}</Tag>
337
+ {/* <Tag>{`motion leave: ${leave}`}</Tag> */}
338
+ </Space>
339
+ {contextHolder}
340
+ </ConfigProvider>
341
+ );
342
+ };
343
+ UseModalAfterClose.storyName = "useModal afterClose";
@@ -35,6 +35,7 @@ const HookModal = ({ afterClose, config, ...props }: PropsWithChildren<HookModal
35
35
  }));
36
36
 
37
37
  const { motion } = props;
38
+ /* istanbul ignore next */
38
39
  const mergedMotion =
39
40
  typeof motion === 'undefined' || motion ?
40
41
  {
@@ -4,18 +4,32 @@ import { Button, ConfigProvider } from '../../../index';
4
4
  import Context from './context';
5
5
 
6
6
  function App({ children, globalVars }) {
7
- return <Context.Provider value={{ title: '1111', ...globalVars }}>{children}</Context.Provider>;
7
+ return (
8
+ <div data-cy="notice-container">
9
+ <Context.Provider value={{ title: '1111', ...globalVars }}>{children}</Context.Provider>
10
+ </div>
11
+ );
8
12
  }
9
13
 
10
14
  export default function Demo() {
11
- const [Notice, elements] = useNotification();
15
+ const [notice, elements] = useNotification();
16
+ const config = {
17
+ content: 'Hello World',
18
+ position: 'top',
19
+ title: <Context.Consumer>{({ title }) => <strong>{title}</strong>}</Context.Consumer>,
20
+ duration: 0,
21
+ };
12
22
 
13
23
  const addNotice = () => {
14
- Notice.addNotice({
15
- content: 'Hello World',
16
- position: 'top',
17
- title: <Context.Consumer>{({ title }) => <strong>{title}</strong>}</Context.Consumer>,
18
- });
24
+ const id1 = notice.info(config);
25
+ const id2 = notice.success(config);
26
+ const id3 = notice.warning(config);
27
+ const id4 = notice.error(config);
28
+ const id5 = notice.open(config);
29
+
30
+ // setTimeout(() => {
31
+ // notice.close(id5);
32
+ // }, 1000);
19
33
  };
20
34
 
21
35
  return (
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.1",
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.1",
18
+ "@douyinfe/semi-animation-react": "2.7.1",
19
+ "@douyinfe/semi-foundation": "2.7.1",
20
+ "@douyinfe/semi-icons": "2.7.1",
21
+ "@douyinfe/semi-illustrations": "2.7.1",
22
+ "@douyinfe/semi-theme-default": "2.7.1",
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": "d0dbd6f932a74386b429dedbc32ae92d9e0af7b9",
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.1",
79
79
  "@storybook/addon-knobs": "^6.3.1",
80
80
  "@types/lodash": "^4.14.176",
81
81
  "babel-loader": "^8.2.2",
@@ -196,4 +196,12 @@ describe('RadioGroup', () => {
196
196
  expect(middleRadio.exists(`.${BASE_CLASS_PREFIX}-radio-addon-buttonRadio-middle`)).toEqual(true);
197
197
  expect(largeRadio.exists(`.${BASE_CLASS_PREFIX}-radio-addon-buttonRadio-large`)).toEqual(true);
198
198
  });
199
- });
199
+
200
+ it('does not trigger Maximum update exceeded when setting radio-group\'s value to NaN', () => {
201
+ const radioGroup = mount(
202
+ createRadioGroup({ value: NaN }),
203
+ );
204
+
205
+ expect(radioGroup.exists(`${BASE_CLASS_PREFIX}-radio-checked`)).toEqual(false);
206
+ });
207
+ });
@@ -865,4 +865,25 @@ export const FixWithFieldLossRef = () => {
865
865
  </Form>
866
866
  );
867
867
  }
868
- FixWithFieldLossRef.storyName = '修复 Form Field 丢失 ref 问题 #384';
868
+ FixWithFieldLossRef.storyName = '修复 Form Field 丢失 ref 问题 #384';
869
+
870
+
871
+ export const SwitchValueToNaN = () => {
872
+ const [val, setVal] = useState(1);
873
+
874
+ return (
875
+ <>
876
+ <RadioGroup direction="vertical" aria-label="单选组合示例" value={val}>
877
+ <Radio value={1}>A</Radio>
878
+ <Radio value={2}>B</Radio>
879
+ <Radio value={3}>C</Radio>
880
+ <Radio value={4}>D</Radio>
881
+ </RadioGroup>
882
+ <Space>
883
+ <Button onClick={() => setVal(NaN)}>NaN</Button>
884
+ <Button onClick={() => setVal(2)}>2</Button>
885
+ </Space>
886
+ </>
887
+ );
888
+ }
889
+ SwitchValueToNaN.storyName = 'SwitchValueToNaN';
@@ -97,6 +97,15 @@ class RadioGroup extends BaseComponent<RadioGroupProps, RadioGroupState> {
97
97
  }
98
98
 
99
99
  componentDidUpdate(prevProps: RadioGroupProps) {
100
+ if (typeof prevProps.value === 'number'
101
+ && isNaN(prevProps.value)
102
+ && typeof this.props.value === 'number'
103
+ && isNaN(this.props.value)
104
+ ) {
105
+ // `NaN === NaN` returns false, and this will fail the next if check
106
+ // therefore triggering an infinite loop
107
+ return;
108
+ }
100
109
  if (prevProps.value !== this.props.value) {
101
110
  this.foundation.handlePropValueChange(this.props.value);
102
111
  }
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useRef, useEffect } from 'react';
2
2
 
3
3
  import './select.scss';
4
- import { Input, Select, Button, Icon, Avatar, Checkbox, Form, withField, Space } from '../../index';
4
+ import { Input, Select, Button, Icon, Avatar, Checkbox, Form, withField, Space, Tag } from '../../index';
5
5
  import CustomTrigger from './CustomTrigger';
6
6
  import classNames from 'classnames';
7
7
  import { getHighLightTextHTML } from '../../_utils/index';
@@ -2903,4 +2903,75 @@ export const AutoClearSearchValue = () => {
2903
2903
 
2904
2904
  SelectInputPropsDemo.story = {
2905
2905
  name: 'AutoClearSearchValue',
2906
- };
2906
+ };
2907
+
2908
+
2909
+ export const RenderSelectedItemCallCount = () => {
2910
+ const list = [
2911
+ { "name": "夏可漫", "email": "xiakeman@example.com", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/avatarDemo.jpeg" },
2912
+ { "name": "申悦", "email": "shenyue@example.com", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg" },
2913
+ { "name": "曲晨一", "email": "quchenyi@example.com", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/8bd8224511db085ed74fea37205aede5.jpg" },
2914
+ { "name": "文嘉茂", "email": "wenjiamao@example.com", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/6fbafc2d-e3e6-4cff-a1e2-17709c680624.png" },
2915
+ { "name": "文嘉茂2", "email": "wenjiamao@example.com", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/6fbafc2d-e3e6-4cff-a1e2-17709c680624.png" },
2916
+ { "name": "文嘉茂3", "email": "wenjiamao@example.com", "avatar": "https://sf6-cdn-tos.douyinstatic.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/root-web-sites/6fbafc2d-e3e6-4cff-a1e2-17709c680624.png" },
2917
+ ]
2918
+
2919
+ const renderMultipleWithCustomTag = (optionNode, { onClose }) => {
2920
+ console.count('rerender')
2921
+ const content = (
2922
+ <Tag
2923
+ avatarSrc={optionNode.avatar}
2924
+ avatarShape='circle'
2925
+ closable={true}
2926
+ onClose={onClose}
2927
+ size='large'
2928
+ >
2929
+ {optionNode.name}
2930
+ </Tag>
2931
+ );
2932
+ return {
2933
+ isRenderInTag: false,
2934
+ content
2935
+ };
2936
+ }
2937
+
2938
+ const renderCustomOption = (item, index) => {
2939
+ const optionStyle = {
2940
+ display: 'flex',
2941
+ paddingLeft: 24,
2942
+ paddingTop: 10,
2943
+ paddingBottom: 10
2944
+ }
2945
+ return (
2946
+ <Select.Option key={index} value={item.name} style={optionStyle} showTick={true} {...item} key={item.email}>
2947
+ <Avatar size="small" src={item.avatar} />
2948
+ <div style={{ marginLeft: 8 }}>
2949
+ <div style={{ fontSize: 14 }}>{item.name}</div>
2950
+ <div style={{ color: 'var(--color-text-2)', fontSize: 12, lineHeight: '16px', fontWeight: 'normal' }}>{item.email}</div>
2951
+ </div>
2952
+ </Select.Option>
2953
+ )
2954
+ }
2955
+
2956
+ return (
2957
+ <>
2958
+ <Select
2959
+ placeholder='请选择'
2960
+ showClear
2961
+ multiple
2962
+ maxTagCount={2}
2963
+ style={{ width: 280, height: 40 }}
2964
+ onChange={v => console.log(v)}
2965
+ defaultValue={'夏可漫'}
2966
+ renderSelectedItem={renderMultipleWithCustomTag}
2967
+ >
2968
+ {list.map((item, index) => renderCustomOption(item, index))}
2969
+ </Select>
2970
+ </>
2971
+ );
2972
+ }
2973
+
2974
+
2975
+ RenderSelectedItemCallCount.story = {
2976
+ name: 'RenderSelectedItemCallCount',
2977
+ };
package/select/index.tsx CHANGED
@@ -898,8 +898,10 @@ class Select extends BaseComponent<SelectProps, SelectState> {
898
898
  content: optionNode.label,
899
899
  });
900
900
  }
901
+
902
+ const mapItems = maxTagCount ? selectedItems.slice(0, maxTagCount) : selectedItems; // no need to render rest tag when maxTagCount is setting
901
903
 
902
- const tags = selectedItems.map((item, i) => {
904
+ const tags = mapItems.map((item, i) => {
903
905
  const label = item[0];
904
906
  const { value } = item[1];
905
907
  const disabled = item[1].disabled || selectDisabled;
@@ -939,11 +941,11 @@ class Select extends BaseComponent<SelectProps, SelectState> {
939
941
  // [prefixcls + '-selection-text-inactive']: !inputValue && !tags.length,
940
942
  });
941
943
  const placeholderText = placeholder && !inputValue ? <span className={spanCls}>{placeholder}</span> : null;
942
- const n = tags.length > maxTagCount ? maxTagCount : undefined;
944
+ const n = selectedItems.length > maxTagCount ? maxTagCount : undefined;
943
945
 
944
946
  const NotOneLine = !maxTagCount; // Multiple lines (that is, do not set maxTagCount), do not use TagGroup, directly traverse with Tag, otherwise Input cannot follow the correct position
945
947
 
946
- const tagContent = NotOneLine ? tags : <TagGroup tagList={tags} maxTagCount={n} size="large" mode="custom" />;
948
+ const tagContent = NotOneLine ? tags : <TagGroup tagList={tags} maxTagCount={n} restCount={maxTagCount ? selectedItems.length - maxTagCount : undefined} size="large" mode="custom" />;
947
949
 
948
950
  return (
949
951
  <>
@@ -0,0 +1,33 @@
1
+ import React, { useState } from 'react';
2
+ import { Popconfirm, Table } from "@douyinfe/semi-ui";
3
+
4
+ export default function App() {
5
+ const [data, setData] = useState([{ a: 1 }]);
6
+ return (
7
+ <Table
8
+ dataSource={data}
9
+ columns={[
10
+ {
11
+ dataIndex: "a",
12
+ title: "a",
13
+ },
14
+ {
15
+ dataIndex: "b",
16
+ render: () => {
17
+ return (
18
+ <Popconfirm
19
+ onConfirm={() => {
20
+ setTimeout(() => {
21
+ setData([]);
22
+ });
23
+ }}
24
+ >
25
+ 删除
26
+ </Popconfirm>
27
+ );
28
+ },
29
+ },
30
+ ]}
31
+ />
32
+ );
33
+ }
@@ -3,4 +3,5 @@ export { default as FixedColumnsChange } from './FixedColumnsChange';
3
3
  export { default as FixedZIndex } from './FixedZIndex';
4
4
  export { default as FixedHeaderMerge } from './FixedHeaderMerge';
5
5
  export { default as FixedResizable } from './FixedResizable';
6
- export { default as FixedExpandedRow } from './FixedExpandedRow';
6
+ export { default as FixedExpandedRow } from './FixedExpandedRow';
7
+ export { default as FixedMemoryLeak } from './FixedMemoryLeak';
package/tag/group.tsx CHANGED
@@ -14,12 +14,13 @@ export interface TagGroupProps {
14
14
  style?: React.CSSProperties;
15
15
  className?: string;
16
16
  maxTagCount?: number;
17
+ restCount?: number;
17
18
  tagList?: (TagProps | React.ReactNode)[];
18
19
  size?: 'small' | 'large';
19
20
  showPopover?: boolean;
20
21
  popoverProps?: PopoverProps;
21
22
  avatarShape?: AvatarShape;
22
- mode?: string; // TODO: This API is not in the check file
23
+ mode?: string;
23
24
  }
24
25
 
25
26
  export default class TagGroup extends PureComponent<TagGroupProps> {
@@ -35,6 +36,7 @@ export default class TagGroup extends PureComponent<TagGroupProps> {
35
36
  style: PropTypes.object,
36
37
  className: PropTypes.string,
37
38
  maxTagCount: PropTypes.number,
39
+ restCount: PropTypes.number,
38
40
  tagList: PropTypes.array,
39
41
  size: PropTypes.oneOf(tagSize),
40
42
  mode: PropTypes.string,
@@ -77,8 +79,8 @@ export default class TagGroup extends PureComponent<TagGroupProps> {
77
79
  }
78
80
 
79
81
  renderMergeTags(tags: (Tag | React.ReactNode)[]) {
80
- const { maxTagCount, tagList } = this.props;
81
- const n = tagList.length - maxTagCount;
82
+ const { maxTagCount, tagList, restCount } = this.props;
83
+ const n = restCount ? restCount : tagList.length - maxTagCount;
82
84
  let renderTags: (Tag | React.ReactNode)[] = tags;
83
85
 
84
86
  const normalTags: (Tag | React.ReactNode)[] = tags.slice(0, maxTagCount);
@@ -96,3 +96,44 @@ export const _Toast = () => (
96
96
  _Toast.story = {
97
97
  name: 'toast',
98
98
  };
99
+
100
+ const ReachableContext = React.createContext();
101
+
102
+ /**
103
+ * test with cypress
104
+ * @returns
105
+ */
106
+ export const useToastDemo = () => {
107
+ const [toast, contextHolder] = Toast.useToast();
108
+ const config = {
109
+ duration: 0,
110
+ title: 'This is a success message',
111
+ content: <ReachableContext.Consumer>{name => `ReachableContext: ${name}`}</ReachableContext.Consumer>,
112
+ };
113
+
114
+ return (
115
+ <ReachableContext.Provider value="Light">
116
+ <div>
117
+ <Button
118
+ onClick={() => {
119
+ toast.success(config);
120
+ toast.info(config);
121
+ toast.error(config);
122
+ toast.warning(config);
123
+ const id = toast.open(config);
124
+
125
+ setTimeout(() => {
126
+ toast.close(id);
127
+ }, 100);
128
+ }}
129
+ >
130
+ Hook Toast
131
+ </Button>
132
+ </div>
133
+ <div data-cy="context-holder">
134
+ {contextHolder}
135
+ </div>
136
+ </ReachableContext.Provider>
137
+ );
138
+ };
139
+ useToastDemo.storyName = "useToast";
package/tooltip/index.tsx CHANGED
@@ -302,7 +302,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
302
302
  } else {
303
303
  willUpdateStates.visible = visible;
304
304
  }
305
- this.setState(willUpdateStates as TooltipState, () => {
305
+ this.mounted && this.setState(willUpdateStates as TooltipState, () => {
306
306
  cb();
307
307
  });
308
308
  },
@@ -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(() => { });