@douyinfe/semi-ui 2.5.1 → 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 (111) hide show
  1. package/button/__test__/button.test.js +7 -0
  2. package/button/buttonGroup.tsx +5 -2
  3. package/calendar/monthCalendar.tsx +14 -13
  4. package/cascader/__test__/cascader.test.js +159 -81
  5. package/cascader/_story/cascader.stories.js +36 -23
  6. package/cascader/index.tsx +47 -8
  7. package/cascader/item.tsx +25 -5
  8. package/datePicker/_story/v2/InsetInput.jsx +104 -0
  9. package/datePicker/_story/v2/InsetInputE2E.jsx +69 -0
  10. package/datePicker/_story/v2/index.js +2 -0
  11. package/datePicker/dateInput.tsx +102 -13
  12. package/datePicker/datePicker.tsx +95 -16
  13. package/datePicker/index.tsx +15 -0
  14. package/datePicker/insetInput.tsx +76 -0
  15. package/datePicker/month.tsx +14 -7
  16. package/datePicker/monthsGrid.tsx +31 -12
  17. package/datePicker/navigation.tsx +8 -4
  18. package/datePicker/quickControl.tsx +1 -0
  19. package/datePicker/yearAndMonth.tsx +1 -1
  20. package/dist/css/semi.css +120 -8
  21. package/dist/css/semi.min.css +1 -1
  22. package/dist/umd/semi-ui.js +1100 -193
  23. package/dist/umd/semi-ui.js.map +1 -1
  24. package/dist/umd/semi-ui.min.js +1 -1
  25. package/dist/umd/semi-ui.min.js.map +1 -1
  26. package/form/hoc/withField.tsx +1 -1
  27. package/input/_story/input.stories.js +13 -0
  28. package/lib/cjs/_base/base.css +5 -5
  29. package/lib/cjs/button/buttonGroup.d.ts +1 -0
  30. package/lib/cjs/button/buttonGroup.js +6 -2
  31. package/lib/cjs/calendar/monthCalendar.js +21 -5
  32. package/lib/cjs/cascader/index.d.ts +10 -2
  33. package/lib/cjs/cascader/index.js +52 -10
  34. package/lib/cjs/cascader/item.d.ts +6 -2
  35. package/lib/cjs/cascader/item.js +33 -4
  36. package/lib/cjs/datePicker/dateInput.d.ts +9 -4
  37. package/lib/cjs/datePicker/dateInput.js +107 -13
  38. package/lib/cjs/datePicker/datePicker.d.ts +7 -2
  39. package/lib/cjs/datePicker/datePicker.js +138 -30
  40. package/lib/cjs/datePicker/index.js +24 -2
  41. package/lib/cjs/datePicker/insetInput.d.ts +21 -0
  42. package/lib/cjs/datePicker/insetInput.js +80 -0
  43. package/lib/cjs/datePicker/month.d.ts +1 -0
  44. package/lib/cjs/datePicker/month.js +18 -2
  45. package/lib/cjs/datePicker/monthsGrid.js +35 -11
  46. package/lib/cjs/datePicker/navigation.js +8 -0
  47. package/lib/cjs/datePicker/quickControl.js +1 -0
  48. package/lib/cjs/datePicker/yearAndMonth.js +1 -0
  49. package/lib/cjs/form/hoc/withField.js +1 -1
  50. package/lib/cjs/navigation/Item.d.ts +2 -2
  51. package/lib/cjs/navigation/Item.js +8 -6
  52. package/lib/cjs/navigation/SubNav.js +2 -2
  53. package/lib/cjs/scrollList/scrollItem.d.ts +2 -1
  54. package/lib/cjs/scrollList/scrollItem.js +13 -3
  55. package/lib/cjs/table/Body/index.d.ts +2 -0
  56. package/lib/cjs/table/Body/index.js +13 -4
  57. package/lib/cjs/tree/index.js +5 -3
  58. package/lib/cjs/tree/interface.d.ts +1 -0
  59. package/lib/cjs/tree/nodeList.js +2 -1
  60. package/lib/cjs/treeSelect/index.js +7 -3
  61. package/lib/es/_base/base.css +5 -5
  62. package/lib/es/button/buttonGroup.d.ts +1 -0
  63. package/lib/es/button/buttonGroup.js +5 -2
  64. package/lib/es/calendar/monthCalendar.js +22 -5
  65. package/lib/es/cascader/index.d.ts +10 -2
  66. package/lib/es/cascader/index.js +49 -7
  67. package/lib/es/cascader/item.d.ts +6 -2
  68. package/lib/es/cascader/item.js +31 -4
  69. package/lib/es/datePicker/dateInput.d.ts +9 -4
  70. package/lib/es/datePicker/dateInput.js +106 -13
  71. package/lib/es/datePicker/datePicker.d.ts +7 -2
  72. package/lib/es/datePicker/datePicker.js +139 -30
  73. package/lib/es/datePicker/index.js +20 -0
  74. package/lib/es/datePicker/insetInput.d.ts +21 -0
  75. package/lib/es/datePicker/insetInput.js +65 -0
  76. package/lib/es/datePicker/month.d.ts +1 -0
  77. package/lib/es/datePicker/month.js +18 -2
  78. package/lib/es/datePicker/monthsGrid.js +35 -11
  79. package/lib/es/datePicker/navigation.js +8 -0
  80. package/lib/es/datePicker/quickControl.js +2 -0
  81. package/lib/es/datePicker/yearAndMonth.js +1 -0
  82. package/lib/es/form/hoc/withField.js +1 -1
  83. package/lib/es/navigation/Item.d.ts +2 -2
  84. package/lib/es/navigation/Item.js +8 -6
  85. package/lib/es/navigation/SubNav.js +2 -2
  86. package/lib/es/scrollList/scrollItem.d.ts +2 -1
  87. package/lib/es/scrollList/scrollItem.js +13 -3
  88. package/lib/es/table/Body/index.d.ts +2 -0
  89. package/lib/es/table/Body/index.js +13 -4
  90. package/lib/es/tree/index.js +5 -3
  91. package/lib/es/tree/interface.d.ts +1 -0
  92. package/lib/es/tree/nodeList.js +2 -1
  93. package/lib/es/treeSelect/index.js +7 -3
  94. package/navigation/Item.tsx +15 -12
  95. package/navigation/SubNav.tsx +4 -4
  96. package/package.json +9 -9
  97. package/scrollList/_story/ScrollList/index.js +3 -0
  98. package/scrollList/_story/WheelList/index.js +3 -0
  99. package/scrollList/scrollItem.tsx +30 -9
  100. package/table/Body/index.tsx +15 -4
  101. package/table/__test__/table.test.js +18 -0
  102. package/table/_story/v2/FixedExpandedRow/index.jsx +95 -0
  103. package/table/_story/v2/index.js +2 -1
  104. package/tree/__test__/tree.test.js +87 -2
  105. package/tree/_story/tree.stories.js +65 -5
  106. package/tree/index.tsx +4 -2
  107. package/tree/interface.ts +1 -0
  108. package/tree/nodeList.tsx +2 -2
  109. package/treeSelect/__test__/treeSelect.test.js +28 -0
  110. package/treeSelect/_story/treeSelect.stories.js +55 -2
  111. package/treeSelect/index.tsx +11 -3
@@ -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.5.1",
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.5.1",
18
- "@douyinfe/semi-animation-react": "2.5.1",
19
- "@douyinfe/semi-foundation": "2.5.1",
20
- "@douyinfe/semi-icons": "2.5.1",
21
- "@douyinfe/semi-illustrations": "2.5.1",
22
- "@douyinfe/semi-theme-default": "2.5.1",
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": "3f83639f8a4fff7f912a237bf2842cb0944d993c",
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.5.1",
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",
@@ -43,6 +43,7 @@ class ScrollListDemo extends React.Component {
43
43
  type={1}
44
44
  selectedIndex={this.state.selectIndex1}
45
45
  onSelect={this.onSelect}
46
+ aria-label="1"
46
47
  />
47
48
  <ScrollItem
48
49
  mode="normal"
@@ -50,6 +51,7 @@ class ScrollListDemo extends React.Component {
50
51
  type={2}
51
52
  selectedIndex={this.state.selectIndex2}
52
53
  onSelect={this.onSelect}
54
+ aria-label="2"
53
55
  />
54
56
  <ScrollItem
55
57
  mode="normal"
@@ -57,6 +59,7 @@ class ScrollListDemo extends React.Component {
57
59
  type={3}
58
60
  selectedIndex={this.state.selectIndex3}
59
61
  onSelect={this.onSelect}
62
+ aria-label="3"
60
63
  />
61
64
  </ScrollList>
62
65
  );
@@ -92,6 +92,7 @@ class ScrollListDemo extends React.Component {
92
92
  type={1}
93
93
  selectedIndex={this.state.selectIndex1}
94
94
  onSelect={this.onSelectAP}
95
+ aria-label="时段"
95
96
  />
96
97
  <ScrollItem
97
98
  {...commonProps}
@@ -99,6 +100,7 @@ class ScrollListDemo extends React.Component {
99
100
  type={2}
100
101
  selectedIndex={this.state.selectIndex2}
101
102
  onSelect={this.onSelectHour}
103
+ aria-label="小时"
102
104
  />
103
105
  <ScrollItem
104
106
  {...commonProps}
@@ -106,6 +108,7 @@ class ScrollListDemo extends React.Component {
106
108
  type={3}
107
109
  selectedIndex={this.state.selectIndex3}
108
110
  onSelect={this.onSelectMinute}
111
+ aria-label="分钟"
109
112
  />
110
113
  </ScrollList>
111
114
  </div>
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { AriaAttributes } from 'react';
2
2
  import BaseComponent from '../_base/baseComponent';
3
3
  import PropTypes from 'prop-types';
4
4
  import classnames from 'classnames';
@@ -28,6 +28,7 @@ export interface ScrollItemProps<T extends Item> {
28
28
  motion?: Motion;
29
29
  style?: React.CSSProperties;
30
30
  type?: string | number; // used to identify the scrollItem, used internally by the semi component, and does not need to be exposed to the user
31
+ 'aria-label'?: AriaAttributes['aria-label'];
31
32
  }
32
33
 
33
34
  export interface ScrollItemState {
@@ -413,15 +414,15 @@ export default class ScrollItem<T extends Item> extends BaseComponent<ScrollItem
413
414
  const { transform: itemTrans } = item;
414
415
 
415
416
  const transform = typeof itemTrans === 'function' ? itemTrans : commonTrans;
416
-
417
+ const selected = selectedIndex === index;
417
418
  const cls = classnames({
418
- [`${cssClasses.PREFIX}-item-sel`]: selectedIndex === index && mode !== wheelMode,
419
+ [`${cssClasses.PREFIX}-item-sel`]: selected && mode !== wheelMode,
419
420
  [`${cssClasses.PREFIX}-item-disabled`]: Boolean(item.disabled),
420
421
  });
421
422
 
422
423
  let text = '';
423
424
 
424
- if (selectedIndex === index) {
425
+ if (selected) {
425
426
  if (typeof transform === 'function') {
426
427
  text = transform(item.value, item.text);
427
428
  } else {
@@ -441,7 +442,14 @@ export default class ScrollItem<T extends Item> extends BaseComponent<ScrollItem
441
442
 
442
443
  return (
443
444
  // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
444
- <li key={prefixKey + index} {...events} className={cls}>
445
+ <li
446
+ key={prefixKey + index}
447
+ {...events}
448
+ className={cls}
449
+ role="option"
450
+ aria-selected={selected}
451
+ aria-disabled={item.disabled}
452
+ >
445
453
  {text}
446
454
  </li>
447
455
  );
@@ -457,7 +465,14 @@ export default class ScrollItem<T extends Item> extends BaseComponent<ScrollItem
457
465
 
458
466
  return (
459
467
  <div style={style} className={wrapperCls} ref={this._cacheWrapperNode}>
460
- <ul ref={this._cacheListNode}>{inner}</ul>
468
+ <ul
469
+ role="listbox"
470
+ aria-multiselectable={false}
471
+ aria-label={this.props['aria-label']}
472
+ ref={this._cacheListNode}
473
+ >
474
+ {inner}
475
+ </ul>
461
476
  </div>
462
477
  );
463
478
  };
@@ -470,13 +485,13 @@ export default class ScrollItem<T extends Item> extends BaseComponent<ScrollItem
470
485
  const { prependCount, appendCount } = this.state;
471
486
 
472
487
  const prependList = times(prependCount).reduce((arr, num) => {
473
- const items = this.renderItemList(`pre_${ num }_`);
488
+ const items = this.renderItemList(`pre_${num}_`);
474
489
  arr.unshift(...items);
475
490
 
476
491
  return arr;
477
492
  }, []);
478
493
  const appendList = times(appendCount).reduce((arr, num) => {
479
- const items = this.renderItemList(`app_${ num }_`);
494
+ const items = this.renderItemList(`app_${num}_`);
480
495
  arr.push(...items);
481
496
  return arr;
482
497
  }, []);
@@ -500,7 +515,13 @@ export default class ScrollItem<T extends Item> extends BaseComponent<ScrollItem
500
515
  <div className={selectorCls} ref={this._cacheSelectorNode} />
501
516
  <div className={postShadeCls} />
502
517
  <div className={listWrapperCls} ref={this._cacheWrapperNode} onScroll={this.scrollToSelectItem}>
503
- <ul ref={this._cacheListNode} onClick={this.clickToSelectItem}>
518
+ <ul
519
+ role="listbox"
520
+ aria-label={this.props['aria-label']}
521
+ aria-multiselectable={false}
522
+ ref={this._cacheListNode}
523
+ onClick={this.clickToSelectItem}
524
+ >
504
525
  {prependList}
505
526
  {inner}
506
527
  {appendList}
@@ -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
  };
@@ -1930,4 +1930,22 @@ describe(`Table`, () => {
1930
1930
  expect(newExpandedRows.length).toEqual(1);
1931
1931
  expect(table.state(`expandedRowKeys`)).toEqual(['1']);
1932
1932
  });
1933
+
1934
+ it(`test expanded row re-render`, () => {
1935
+ const expandedRowRender = sinon.spy(() => <div>Semi Design</div>);
1936
+ const demo = mount(
1937
+ <Table
1938
+ columns={columns}
1939
+ dataSource={data}
1940
+ expandedRowRender={expandedRowRender}
1941
+ />
1942
+ );
1943
+
1944
+ const table = demo.find(BaseTable);
1945
+
1946
+ const expandIcons = demo.find(`.semi-table-tbody .semi-table-row .semi-table-expand-icon`);
1947
+ expandIcons.at(0).simulate('click');
1948
+ expandIcons.at(1).simulate('click');
1949
+ expect(expandedRowRender.calledTwice).toBeTruthy();
1950
+ });
1933
1951
  });
@@ -0,0 +1,95 @@
1
+ import React from 'react';
2
+ import { Table, Avatar } from '@douyinfe/semi-ui';
3
+ import { IconMore } from '@douyinfe/semi-icons';
4
+
5
+ const columns = [
6
+ {
7
+ title: '标题',
8
+ width: 500,
9
+ dataIndex: 'name',
10
+ render: (text, record, index) => {
11
+ return (
12
+ <span>
13
+ <Avatar size="small" shape="square" src={record.nameIconSrc} style={{ marginRight: 12 }}></Avatar>
14
+ {text}
15
+ </span>
16
+ );
17
+ },
18
+ },
19
+ {
20
+ title: '大小',
21
+ dataIndex: 'size',
22
+ },
23
+ {
24
+ title: '所有者',
25
+ dataIndex: 'owner',
26
+ render: (text, record, index) => {
27
+ return (
28
+ <div>
29
+ <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
30
+ {typeof text === 'string' && text.slice(0, 1)}
31
+ </Avatar>
32
+ {text}
33
+ </div>
34
+ );
35
+ },
36
+ },
37
+ {
38
+ title: '更新日期',
39
+ dataIndex: 'updateTime',
40
+ },
41
+ {
42
+ title: '',
43
+ dataIndex: 'operate',
44
+ render: () => {
45
+ return <IconMore />;
46
+ },
47
+ },
48
+ ];
49
+
50
+ const data = [
51
+ {
52
+ key: '1',
53
+ name: 'Semi Design 设计稿.fig',
54
+ nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
55
+ size: '2M',
56
+ owner: '姜鹏志',
57
+ updateTime: '2020-02-02 05:13',
58
+ avatarBg: 'grey',
59
+ },
60
+ {
61
+ key: '2',
62
+ name: 'Semi Design 分享演示文稿',
63
+ nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
64
+ size: '2M',
65
+ owner: '郝宣',
66
+ updateTime: '2020-01-17 05:31',
67
+ avatarBg: 'red',
68
+ },
69
+ {
70
+ key: '3',
71
+ name: '设计文档',
72
+ nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
73
+ size: '34KB',
74
+ owner: 'Zoey Edwards',
75
+ updateTime: '2020-01-26 11:01',
76
+ avatarBg: 'light-blue',
77
+ },
78
+ ];
79
+
80
+ export default function App() {
81
+ const expandRowRender = (record, index) => {
82
+ console.log('render', index);
83
+ return <div>semi design</div>;
84
+ };
85
+
86
+ return (
87
+ <Table
88
+ rowKey="name"
89
+ columns={columns}
90
+ dataSource={data}
91
+ expandedRowRender={expandRowRender}
92
+ pagination={false}
93
+ />
94
+ );
95
+ }
@@ -2,4 +2,5 @@ export { default as DefaultFilteredValue } from './defaultFilteredValue';
2
2
  export { default as FixedColumnsChange } from './FixedColumnsChange';
3
3
  export { default as FixedZIndex } from './FixedZIndex';
4
4
  export { default as FixedHeaderMerge } from './FixedHeaderMerge';
5
- export { default as FixedResizable } from './FixedResizable';
5
+ export { default as FixedResizable } from './FixedResizable';
6
+ export { default as FixedExpandedRow } from './FixedExpandedRow';
@@ -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
+ }