@douyinfe/semi-ui 2.6.0-beta.0 → 2.7.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 (105) hide show
  1. package/button/__test__/button.test.js +7 -0
  2. package/button/buttonGroup.tsx +5 -2
  3. package/button/index.tsx +1 -1
  4. package/cascader/__test__/cascader.test.js +159 -81
  5. package/cascader/_story/cascader.stories.js +36 -23
  6. package/cascader/index.tsx +26 -5
  7. package/datePicker/_story/v2/InsetInput.jsx +104 -0
  8. package/datePicker/_story/v2/InsetInputE2E.jsx +69 -0
  9. package/datePicker/_story/v2/index.js +2 -0
  10. package/datePicker/dateInput.tsx +95 -9
  11. package/datePicker/datePicker.tsx +89 -15
  12. package/datePicker/index.tsx +15 -0
  13. package/datePicker/insetInput.tsx +76 -0
  14. package/datePicker/monthsGrid.tsx +14 -7
  15. package/dist/css/semi.css +105 -2
  16. package/dist/css/semi.min.css +1 -1
  17. package/dist/umd/semi-ui.js +949 -163
  18. package/dist/umd/semi-ui.js.map +1 -1
  19. package/dist/umd/semi-ui.min.js +1 -1
  20. package/dist/umd/semi-ui.min.js.map +1 -1
  21. package/form/hooks/useFormApi.tsx +3 -2
  22. package/input/_story/input.stories.js +13 -0
  23. package/lib/cjs/button/Button.d.ts +4 -4
  24. package/lib/cjs/button/buttonGroup.d.ts +3 -2
  25. package/lib/cjs/button/buttonGroup.js +6 -2
  26. package/lib/cjs/button/index.d.ts +5 -6
  27. package/lib/cjs/cascader/index.d.ts +1 -0
  28. package/lib/cjs/cascader/index.js +38 -9
  29. package/lib/cjs/datePicker/dateInput.d.ts +9 -2
  30. package/lib/cjs/datePicker/dateInput.js +92 -9
  31. package/lib/cjs/datePicker/datePicker.d.ts +7 -2
  32. package/lib/cjs/datePicker/datePicker.js +123 -18
  33. package/lib/cjs/datePicker/index.js +24 -2
  34. package/lib/cjs/datePicker/insetInput.d.ts +21 -0
  35. package/lib/cjs/datePicker/insetInput.js +80 -0
  36. package/lib/cjs/datePicker/monthsGrid.js +19 -7
  37. package/lib/cjs/form/hooks/useFormApi.d.ts +2 -1
  38. package/lib/cjs/iconButton/index.d.ts +2 -2
  39. package/lib/cjs/navigation/Item.d.ts +2 -2
  40. package/lib/cjs/navigation/Item.js +8 -6
  41. package/lib/cjs/navigation/SubNav.js +2 -2
  42. package/lib/cjs/radio/radioGroup.js +6 -0
  43. package/lib/cjs/select/index.js +5 -2
  44. package/lib/cjs/table/Body/index.d.ts +2 -0
  45. package/lib/cjs/table/Body/index.js +13 -4
  46. package/lib/cjs/tag/group.d.ts +2 -0
  47. package/lib/cjs/tag/group.js +4 -2
  48. package/lib/cjs/tooltip/index.js +6 -2
  49. package/lib/cjs/tree/index.js +5 -3
  50. package/lib/cjs/tree/interface.d.ts +1 -0
  51. package/lib/cjs/tree/nodeList.js +3 -1
  52. package/lib/cjs/treeSelect/index.js +11 -3
  53. package/lib/es/button/Button.d.ts +4 -4
  54. package/lib/es/button/buttonGroup.d.ts +3 -2
  55. package/lib/es/button/buttonGroup.js +5 -2
  56. package/lib/es/button/index.d.ts +5 -6
  57. package/lib/es/cascader/index.d.ts +1 -0
  58. package/lib/es/cascader/index.js +35 -6
  59. package/lib/es/datePicker/dateInput.d.ts +9 -2
  60. package/lib/es/datePicker/dateInput.js +91 -9
  61. package/lib/es/datePicker/datePicker.d.ts +7 -2
  62. package/lib/es/datePicker/datePicker.js +124 -18
  63. package/lib/es/datePicker/index.js +20 -0
  64. package/lib/es/datePicker/insetInput.d.ts +21 -0
  65. package/lib/es/datePicker/insetInput.js +65 -0
  66. package/lib/es/datePicker/monthsGrid.js +19 -7
  67. package/lib/es/form/hooks/useFormApi.d.ts +2 -1
  68. package/lib/es/iconButton/index.d.ts +2 -2
  69. package/lib/es/navigation/Item.d.ts +2 -2
  70. package/lib/es/navigation/Item.js +8 -6
  71. package/lib/es/navigation/SubNav.js +2 -2
  72. package/lib/es/radio/radioGroup.js +6 -0
  73. package/lib/es/select/index.js +5 -2
  74. package/lib/es/table/Body/index.d.ts +2 -0
  75. package/lib/es/table/Body/index.js +13 -4
  76. package/lib/es/tag/group.d.ts +2 -0
  77. package/lib/es/tag/group.js +4 -2
  78. package/lib/es/tooltip/index.js +6 -2
  79. package/lib/es/tree/index.js +5 -3
  80. package/lib/es/tree/interface.d.ts +1 -0
  81. package/lib/es/tree/nodeList.js +3 -1
  82. package/lib/es/treeSelect/index.js +11 -3
  83. package/navigation/Item.tsx +15 -12
  84. package/navigation/SubNav.tsx +4 -4
  85. package/package.json +9 -9
  86. package/radio/__test__/radioGroup.test.jsx +9 -1
  87. package/radio/_story/radio.stories.js +22 -1
  88. package/radio/radioGroup.tsx +9 -0
  89. package/select/_story/select.stories.js +73 -2
  90. package/select/index.tsx +5 -3
  91. package/table/Body/index.tsx +15 -4
  92. package/table/__test__/table.test.js +18 -0
  93. package/table/_story/v2/FixedExpandedRow/index.jsx +95 -0
  94. package/table/_story/v2/index.js +2 -1
  95. package/tag/group.tsx +5 -3
  96. package/tooltip/_story/tooltip.stories.js +702 -625
  97. package/tooltip/index.tsx +2 -2
  98. package/tree/__test__/tree.test.js +87 -2
  99. package/tree/_story/tree.stories.js +65 -5
  100. package/tree/index.tsx +4 -2
  101. package/tree/interface.ts +1 -0
  102. package/tree/nodeList.tsx +3 -2
  103. package/treeSelect/__test__/treeSelect.test.js +28 -0
  104. package/treeSelect/_story/treeSelect.stories.js +55 -2
  105. package/treeSelect/index.tsx +14 -3
@@ -688,7 +688,9 @@ class Select extends BaseComponent {
688
688
  });
689
689
  }
690
690
 
691
- const tags = _mapInstanceProperty(selectedItems).call(selectedItems, (item, i) => {
691
+ const mapItems = maxTagCount ? _sliceInstanceProperty(selectedItems).call(selectedItems, 0, maxTagCount) : selectedItems; // no need to render rest tag when maxTagCount is setting
692
+
693
+ const tags = _mapInstanceProperty(mapItems).call(mapItems, (item, i) => {
692
694
  const label = item[0];
693
695
  const {
694
696
  value
@@ -747,12 +749,13 @@ class Select extends BaseComponent {
747
749
  const placeholderText = placeholder && !inputValue ? /*#__PURE__*/React.createElement("span", {
748
750
  className: spanCls
749
751
  }, placeholder) : null;
750
- const n = tags.length > maxTagCount ? maxTagCount : undefined;
752
+ const n = selectedItems.length > maxTagCount ? maxTagCount : undefined;
751
753
  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
752
754
 
753
755
  const tagContent = NotOneLine ? tags : /*#__PURE__*/React.createElement(TagGroup, {
754
756
  tagList: tags,
755
757
  maxTagCount: n,
758
+ restCount: maxTagCount ? selectedItems.length - maxTagCount : undefined,
756
759
  size: "large",
757
760
  mode: "custom"
758
761
  });
@@ -43,6 +43,8 @@ export interface BodyState {
43
43
  }
44
44
  export interface BodyContext {
45
45
  getVirtualizedListRef: GetVirtualizedListRef;
46
+ flattenedColumns: ColumnProps[];
47
+ getCellWidths: (flattenedColumns: ColumnProps[]) => number[];
46
48
  }
47
49
  declare const _default: React.ForwardRefExoticComponent<Omit<BodyProps, "forwardedRef"> & React.RefAttributes<HTMLDivElement>>;
48
50
  export default _default;
@@ -389,8 +389,13 @@ class Body extends BaseComponent {
389
389
  const {
390
390
  flattenedColumns,
391
391
  getCellWidths
392
- } = _this.context;
393
- const cellWidths = getCellWidths(flattenedColumns);
392
+ } = _this.context; // we use memoized cellWidths to avoid re-render expanded row (fix #686)
393
+
394
+ if (flattenedColumns !== _this.flattenedColumns) {
395
+ _this.flattenedColumns = flattenedColumns;
396
+ _this.cellWidths = getCellWidths(flattenedColumns);
397
+ }
398
+
394
399
  return /*#__PURE__*/React.createElement(ExpandedRow, {
395
400
  style: style,
396
401
  components: components,
@@ -402,7 +407,7 @@ class Body extends BaseComponent {
402
407
  index: index,
403
408
  virtualized: virtualized,
404
409
  key: genExpandedRowKey(key),
405
- cellWidths: cellWidths
410
+ cellWidths: _this.cellWidths
406
411
  });
407
412
  };
408
413
  /**
@@ -580,7 +585,9 @@ class Body extends BaseComponent {
580
585
  };
581
586
  this.listRef = /*#__PURE__*/React.createRef();
582
587
  const {
583
- getVirtualizedListRef
588
+ getVirtualizedListRef,
589
+ flattenedColumns,
590
+ getCellWidths
584
591
  } = context;
585
592
 
586
593
  if (getVirtualizedListRef) {
@@ -592,6 +599,8 @@ class Body extends BaseComponent {
592
599
  }
593
600
 
594
601
  this.foundation = new BodyFoundation(this.adapter);
602
+ this.flattenedColumns = flattenedColumns;
603
+ this.cellWidths = getCellWidths(flattenedColumns);
595
604
  this.observer = null;
596
605
  }
597
606
 
@@ -7,6 +7,7 @@ export interface TagGroupProps {
7
7
  style?: React.CSSProperties;
8
8
  className?: string;
9
9
  maxTagCount?: number;
10
+ restCount?: number;
10
11
  tagList?: (TagProps | React.ReactNode)[];
11
12
  size?: 'small' | 'large';
12
13
  showPopover?: boolean;
@@ -26,6 +27,7 @@ export default class TagGroup extends PureComponent<TagGroupProps> {
26
27
  style: PropTypes.Requireable<object>;
27
28
  className: PropTypes.Requireable<string>;
28
29
  maxTagCount: PropTypes.Requireable<number>;
30
+ restCount: PropTypes.Requireable<number>;
29
31
  tagList: PropTypes.Requireable<any[]>;
30
32
  size: PropTypes.Requireable<string>;
31
33
  mode: PropTypes.Requireable<string>;
@@ -46,9 +46,10 @@ export default class TagGroup extends PureComponent {
46
46
  renderMergeTags(tags) {
47
47
  const {
48
48
  maxTagCount,
49
- tagList
49
+ tagList,
50
+ restCount
50
51
  } = this.props;
51
- const n = tagList.length - maxTagCount;
52
+ const n = restCount ? restCount : tagList.length - maxTagCount;
52
53
  let renderTags = tags;
53
54
 
54
55
  const normalTags = _sliceInstanceProperty(tags).call(tags, 0, maxTagCount);
@@ -128,6 +129,7 @@ TagGroup.propTypes = {
128
129
  style: PropTypes.object,
129
130
  className: PropTypes.string,
130
131
  maxTagCount: PropTypes.number,
132
+ restCount: PropTypes.number,
131
133
  tagList: PropTypes.array,
132
134
  size: PropTypes.oneOf(tagSize),
133
135
  mode: PropTypes.string,
@@ -184,7 +184,9 @@ export default class Tooltip extends BaseComponent {
184
184
  } = _ref;
185
185
  return /*#__PURE__*/React.createElement("div", _Object$assign({
186
186
  className: classNames(className, animateCls),
187
- style: _Object$assign(_Object$assign(_Object$assign({}, animateStyle), {
187
+ style: _Object$assign(_Object$assign(_Object$assign({
188
+ visibility: 'visible'
189
+ }, animateStyle), {
188
190
  transformOrigin
189
191
  }), style)
190
192
  }, portalEventSet, animateEvents, {
@@ -196,7 +198,9 @@ export default class Tooltip extends BaseComponent {
196
198
  className: className
197
199
  }, portalEventSet, {
198
200
  "x-placement": placement,
199
- style: style
201
+ style: _Object$assign({
202
+ visibility: motion ? undefined : 'visible'
203
+ }, style)
200
204
  }), content, icon);
201
205
  return /*#__PURE__*/React.createElement(Portal, {
202
206
  getPopupContainer: this.props.getPopupContainer,
@@ -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>>;
@@ -22,6 +22,7 @@ export default class NodeList extends PureComponent {
22
22
  super(props);
23
23
 
24
24
  this.onMotionEnd = () => {
25
+ typeof this.props.onMotionEnd === 'function' && this.props.onMotionEnd();
25
26
  this.setState({
26
27
  transitionNodes: []
27
28
  });
@@ -83,12 +84,13 @@ export default class NodeList extends PureComponent {
83
84
  const {
84
85
  flattenNodes,
85
86
  motionType,
87
+ searchTargetIsDeep,
86
88
  renderTreeNode
87
89
  } = this.props;
88
90
  const {
89
91
  transitionNodes
90
92
  } = this.state;
91
- const mapData = transitionNodes.length ? transitionNodes : flattenNodes;
93
+ const mapData = transitionNodes.length && !searchTargetIsDeep ? transitionNodes : flattenNodes;
92
94
 
93
95
  const options = _mapInstanceProperty(mapData).call(mapData, treeNode => {
94
96
  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
  });
@@ -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;
@@ -4,12 +4,16 @@ import BaseComponent, { BaseProps } from '../_base/baseComponent';
4
4
  import React from 'react';
5
5
  import PropTypes from 'prop-types';
6
6
  import cls from 'classnames';
7
- import { times, noop } from 'lodash';
7
+ import { noop, times } from 'lodash';
8
8
 
9
9
  import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined';
10
10
  import { cloneDeep, isSemiIcon } from '../_utils';
11
- import ItemFoundation, { ItemProps, SelectedItemProps, ItemAdapter } from '@douyinfe/semi-foundation/navigation/itemFoundation';
12
- import { strings, cssClasses } from '@douyinfe/semi-foundation/navigation/constants';
11
+ import ItemFoundation, {
12
+ ItemAdapter,
13
+ ItemProps,
14
+ SelectedItemProps
15
+ } from '@douyinfe/semi-foundation/navigation/itemFoundation';
16
+ import { cssClasses, strings } from '@douyinfe/semi-foundation/navigation/constants';
13
17
 
14
18
  import Tooltip from '../tooltip';
15
19
  import NavContext from './nav-context';
@@ -114,7 +118,7 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
114
118
  };
115
119
  }
116
120
 
117
- renderIcon(icon: React.ReactNode, pos: string, isToggleIcon = false) {
121
+ renderIcon(icon: React.ReactNode, pos: string, isToggleIcon = false, key: number | string = 0) {
118
122
  if (this.props.isSubNav) {
119
123
  return null;
120
124
  }
@@ -134,8 +138,8 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
134
138
  });
135
139
 
136
140
  return (
137
- <i className={className}>
138
- {isSemiIcon(icon) ? React.cloneElement((icon as React.ReactElement), {size: (icon as React.ReactElement).props.size || iconSize}) : icon}
141
+ <i className={className} key={key}>
142
+ {isSemiIcon(icon) ? React.cloneElement((icon as React.ReactElement), { size: (icon as React.ReactElement).props.size || iconSize }) : icon}
139
143
  </i>
140
144
  );
141
145
  }
@@ -189,7 +193,6 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
189
193
  const selected = this.adapter.getSelected();
190
194
 
191
195
 
192
-
193
196
  let itemChildren = null;
194
197
  if (!isNullOrUndefined(children)) {
195
198
  itemChildren = children;
@@ -197,15 +200,15 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
197
200
  let placeholderIcons = null;
198
201
  if (mode === strings.MODE_VERTICAL && !limitIndent && !isCollapsed) {
199
202
  const iconAmount = (icon && !indent) ? level : level - 1;
200
- placeholderIcons = times(iconAmount, () => this.renderIcon(null, strings.ICON_POS_RIGHT, false));
203
+ placeholderIcons = times(iconAmount, (index) => this.renderIcon(null, strings.ICON_POS_RIGHT, false, index));
201
204
  }
202
205
  itemChildren = (
203
206
  <>
204
207
  {placeholderIcons}
205
- {this.context.toggleIconPosition === strings.TOGGLE_ICON_LEFT && this.renderIcon(toggleIcon, strings.ICON_POS_RIGHT, true)}
206
- {icon || indent || isInSubNav ? this.renderIcon(icon, strings.ICON_POS_LEFT) : null}
208
+ {this.context.toggleIconPosition === strings.TOGGLE_ICON_LEFT && this.renderIcon(toggleIcon, strings.ICON_POS_RIGHT, true, 'key-toggle-pos-right')}
209
+ {icon || indent || isInSubNav ? this.renderIcon(icon, strings.ICON_POS_LEFT, false, 'key-position-left') : null}
207
210
  {!isNullOrUndefined(text) ? <span className={`${cssClasses.PREFIX}-item-text`}>{text}</span> : ''}
208
- {this.context.toggleIconPosition === strings.TOGGLE_ICON_RIGHT && this.renderIcon(toggleIcon, strings.ICON_POS_RIGHT, true)}
211
+ {this.context.toggleIconPosition === strings.TOGGLE_ICON_RIGHT && this.renderIcon(toggleIcon, strings.ICON_POS_RIGHT, true, 'key-toggle-pos-right')}
209
212
  </>
210
213
  );
211
214
  }
@@ -246,7 +249,7 @@ export default class NavItem extends BaseComponent<NavItemProps, NavItemState> {
246
249
  );
247
250
  } else {
248
251
  // Items are divided into normal and sub-wrap
249
- const popoverItemCls = cls(`${className || `${clsPrefix }-normal`}`, {
252
+ const popoverItemCls = cls(`${className || `${clsPrefix}-normal`}`, {
250
253
  [clsPrefix]: true,
251
254
  [`${clsPrefix}-sub`]: isSubNav,
252
255
  [`${clsPrefix}-selected`]: selected && !isSubNav,
@@ -240,15 +240,15 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
240
240
  if (mode === strings.MODE_VERTICAL && !limitIndent && !isCollapsed) {
241
241
  /* Different icons' amount means different indents.*/
242
242
  const iconAmount = (icon && !indent) ? level : level - 1;
243
- placeholderIcons = times(iconAmount, index => this.renderIcon(null, strings.ICON_POS_RIGHT, false, undefined, index));
243
+ placeholderIcons = times(iconAmount, index => this.renderIcon(null, strings.ICON_POS_RIGHT, false, false, index));
244
244
  }
245
245
 
246
246
  const titleDiv = (
247
247
  <div
248
248
  role="menuitem"
249
249
  tabIndex={-1}
250
- ref={this.setTitleRef as any}
251
- className={titleCls}
250
+ ref={this.setTitleRef as any}
251
+ className={titleCls}
252
252
  onClick={this.handleClick}
253
253
  onKeyPress={this.handleKeyPress}
254
254
  >
@@ -256,7 +256,7 @@ export default class SubNav extends BaseComponent<SubNavProps, SubNavState> {
256
256
  {placeholderIcons}
257
257
  {this.context.toggleIconPosition === strings.TOGGLE_ICON_LEFT && this.renderIcon(toggleIconType, strings.ICON_POS_RIGHT, withTransition, true, 'key-toggle-position-left')}
258
258
  {icon || indent || (isInSubNav && mode !== strings.MODE_HORIZONTAL)
259
- ? this.renderIcon(icon, strings.ICON_POS_LEFT, undefined, undefined, 'key-inSubNav-position-left')
259
+ ? this.renderIcon(icon, strings.ICON_POS_LEFT, false, false, 'key-inSubNav-position-left')
260
260
  : null}
261
261
  <span className={`${prefixCls}-item-text`}>{text}</span>
262
262
  {this.context.toggleIconPosition === strings.TOGGLE_ICON_RIGHT && this.renderIcon(toggleIconType, strings.ICON_POS_RIGHT, withTransition, true, 'key-toggle-position-right')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@douyinfe/semi-ui",
3
- "version": "2.6.0-beta.0",
3
+ "version": "2.7.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-beta.0",
18
- "@douyinfe/semi-animation-react": "2.6.0-beta.0",
19
- "@douyinfe/semi-foundation": "2.6.0-beta.0",
20
- "@douyinfe/semi-icons": "2.6.0-beta.0",
21
- "@douyinfe/semi-illustrations": "2.6.0-beta.0",
22
- "@douyinfe/semi-theme-default": "2.6.0-beta.0",
17
+ "@douyinfe/semi-animation": "2.7.0",
18
+ "@douyinfe/semi-animation-react": "2.7.0",
19
+ "@douyinfe/semi-foundation": "2.7.0",
20
+ "@douyinfe/semi-icons": "2.7.0",
21
+ "@douyinfe/semi-illustrations": "2.7.0",
22
+ "@douyinfe/semi-theme-default": "2.7.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": "49d107f759f3610a471c34bb9568ad9408aa2cb0",
72
+ "gitHead": "b6e4e0a1e22d4dabe68cbf7e3d2cfd0202da0424",
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-beta.0",
78
+ "@douyinfe/semi-scss-compile": "2.7.0",
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
  <>
@@ -89,6 +89,8 @@ export interface BodyState {
89
89
 
90
90
  export interface BodyContext {
91
91
  getVirtualizedListRef: GetVirtualizedListRef;
92
+ flattenedColumns: ColumnProps[];
93
+ getCellWidths: (flattenedColumns: ColumnProps[]) => number[];
92
94
  }
93
95
 
94
96
  class Body extends BaseComponent<BodyProps, BodyState> {
@@ -128,6 +130,8 @@ class Body extends BaseComponent<BodyProps, BodyState> {
128
130
  listRef: React.MutableRefObject<any>;
129
131
  observer: ResizeObserver;
130
132
  foundation: BodyFoundation;
133
+ cellWidths: number[];
134
+ flattenedColumns: ColumnProps[];
131
135
  constructor(props: BodyProps, context: BodyContext) {
132
136
  super(props);
133
137
  this.ref = React.createRef();
@@ -142,7 +146,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
142
146
  };
143
147
 
144
148
  this.listRef = React.createRef();
145
- const { getVirtualizedListRef } = context;
149
+ const { getVirtualizedListRef, flattenedColumns, getCellWidths } = context;
146
150
  if (getVirtualizedListRef) {
147
151
  if (props.virtualized) {
148
152
  getVirtualizedListRef(this.listRef);
@@ -152,6 +156,8 @@ class Body extends BaseComponent<BodyProps, BodyState> {
152
156
  }
153
157
  }
154
158
  this.foundation = new BodyFoundation(this.adapter);
159
+ this.flattenedColumns = flattenedColumns;
160
+ this.cellWidths = getCellWidths(flattenedColumns);
155
161
  this.observer = null;
156
162
  }
157
163
 
@@ -199,7 +205,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
199
205
  this.observer.unobserve(bodyWrapDOM);
200
206
  this.observer = null;
201
207
  }
202
- }
208
+ },
203
209
  };
204
210
  }
205
211
 
@@ -494,7 +500,12 @@ class Body extends BaseComponent<BodyProps, BodyState> {
494
500
  }
495
501
 
496
502
  const { flattenedColumns, getCellWidths } = this.context;
497
- const cellWidths = getCellWidths(flattenedColumns);
503
+
504
+ // we use memoized cellWidths to avoid re-render expanded row (fix #686)
505
+ if (flattenedColumns !== this.flattenedColumns) {
506
+ this.flattenedColumns = flattenedColumns;
507
+ this.cellWidths = getCellWidths(flattenedColumns);
508
+ }
498
509
 
499
510
  return (
500
511
  <ExpandedRow
@@ -508,7 +519,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
508
519
  index={index}
509
520
  virtualized={virtualized}
510
521
  key={genExpandedRowKey(key)}
511
- cellWidths={cellWidths}
522
+ cellWidths={this.cellWidths}
512
523
  />
513
524
  );
514
525
  };