index.tsx 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. /* eslint-disable eqeqeq */
  2. /* eslint-disable @typescript-eslint/member-ordering */
  3. /* eslint-disable max-len */
  4. import React, { ReactNode } from 'react';
  5. import PropTypes from 'prop-types';
  6. import { get, size, isMap, each, isEqual, pick, isNull } from 'lodash-es';
  7. import classnames from 'classnames';
  8. import { VariableSizeList as List } from 'react-window';
  9. import {
  10. arrayAdd,
  11. getRecordKey,
  12. isExpanded,
  13. isSelected,
  14. isDisabled,
  15. getRecord,
  16. genExpandedRowKey,
  17. getDefaultVirtualizedRowConfig
  18. } from '@douyinfe/semi-foundation/table/utils';
  19. import BodyFoundation, { BodyAdapter, FlattenData, GroupFlattenData } from '@douyinfe/semi-foundation/table/bodyFoundation';
  20. import { strings } from '@douyinfe/semi-foundation/table/constants';
  21. import Store from '@douyinfe/semi-foundation/utils/Store';
  22. import BaseComponent, { BaseProps } from '../../_base/baseComponent';
  23. import { logger } from '../utils';
  24. import ColGroup from '../ColGroup';
  25. import BaseRow from './BaseRow';
  26. import ExpandedRow from './ExpandedRow';
  27. import SectionRow from './SectionRow';
  28. import TableHeader from '../TableHeader';
  29. import ConfigContext from '../../configProvider/context';
  30. import TableContext from '../table-context';
  31. import {
  32. ExpandedRowRender,
  33. Virtualized,
  34. GetVirtualizedListRef,
  35. ColumnProps,
  36. Size,
  37. BodyScrollEvent,
  38. Scroll,
  39. Fixed,
  40. TableComponents,
  41. RowExpandable,
  42. VirtualizedOnScroll,
  43. Direction,
  44. RowKey
  45. } from '../interface';
  46. export interface BodyProps extends BaseProps {
  47. anyColumnFixed?: boolean;
  48. columns?: ColumnProps[];
  49. dataSource?: Record<string, any>[];
  50. disabledRowKeysSet: Set<any>; // required
  51. emptySlot?: ReactNode;
  52. expandedRowKeys?: (string | number)[];
  53. expandedRowRender?: ExpandedRowRender<Record<string, any>>;
  54. fixed?: Fixed;
  55. forwardedRef?: React.MutableRefObject<HTMLDivElement> | ((instance: any) => void);
  56. handleBodyScroll?: (e: BodyScrollEvent) => void;
  57. handleWheel?: (e: React.WheelEvent<HTMLDivElement>) => void;
  58. includeHeader?: boolean;
  59. prefixCls?: string;
  60. scroll?: Scroll;
  61. selectedRowKeysSet: Set<any>; // required
  62. showHeader?: boolean;
  63. size?: Size;
  64. virtualized?: Virtualized;
  65. components?: TableComponents;
  66. store: Store;
  67. groups?: Map<string, Record<string, any>[]>[];
  68. rowKey?: RowKey<Record<string, any>>;
  69. childrenRecordName?: string;
  70. rowExpandable?: RowExpandable<Record<string, any>>;
  71. renderExpandIcon: (record: Record<string, any>, isNested: boolean) => ReactNode | null;
  72. headerRef?: React.MutableRefObject<HTMLDivElement> | ((instance: any) => void);
  73. onScroll?: VirtualizedOnScroll;
  74. }
  75. export interface BodyState {
  76. virtualizedData?: Array<FlattenData | GroupFlattenData>;
  77. cache?: {
  78. virtualizedScrollTop?: number;
  79. virtualizedScrollLeft?: number;
  80. };
  81. cachedExpandBtnShouldInRow?: boolean;
  82. cachedExpandRelatedProps?: any[];
  83. }
  84. export interface BodyContext {
  85. getVirtualizedListRef: GetVirtualizedListRef;
  86. }
  87. class Body extends BaseComponent<BodyProps, BodyState> {
  88. static contextType = TableContext;
  89. static propTypes = {
  90. anyColumnFixed: PropTypes.bool,
  91. childrenRecordName: PropTypes.string,
  92. columns: PropTypes.array,
  93. components: PropTypes.object,
  94. dataSource: PropTypes.array,
  95. disabledRowKeysSet: PropTypes.instanceOf(Set).isRequired,
  96. emptySlot: PropTypes.node,
  97. expandRowByClick: PropTypes.bool,
  98. expandedRowKeys: PropTypes.array,
  99. expandedRowRender: PropTypes.func,
  100. fixed: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
  101. forwardedRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
  102. groups: PropTypes.instanceOf(Map),
  103. handleBodyScroll: PropTypes.func,
  104. handleWheel: PropTypes.func,
  105. headerRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
  106. includeHeader: PropTypes.bool,
  107. onScroll: PropTypes.func,
  108. prefixCls: PropTypes.string,
  109. renderExpandIcon: PropTypes.func,
  110. rowExpandable: PropTypes.func,
  111. rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.func]),
  112. scroll: PropTypes.object,
  113. selectedRowKeysSet: PropTypes.instanceOf(Set).isRequired,
  114. showHeader: PropTypes.bool,
  115. size: PropTypes.string,
  116. store: PropTypes.object,
  117. virtualized: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
  118. };
  119. ref: React.MutableRefObject<any>;
  120. listRef: React.MutableRefObject<any>;
  121. observer: ResizeObserver;
  122. foundation: BodyFoundation;
  123. constructor(props: BodyProps, context: BodyContext) {
  124. super(props);
  125. this.ref = React.createRef();
  126. this.state = {
  127. virtualizedData: [],
  128. cache: {
  129. virtualizedScrollTop: null,
  130. virtualizedScrollLeft: null,
  131. },
  132. cachedExpandBtnShouldInRow: null,
  133. cachedExpandRelatedProps: [],
  134. };
  135. this.listRef = React.createRef();
  136. const { getVirtualizedListRef } = context;
  137. if (getVirtualizedListRef) {
  138. if (props.virtualized) {
  139. getVirtualizedListRef(this.listRef);
  140. } else {
  141. console.warn('getVirtualizedListRef only works with virtualized. ' +
  142. 'See https://semi.design/zh-CN/show/table for more information.');
  143. }
  144. }
  145. this.foundation = new BodyFoundation(this.adapter);
  146. this.observer = null;
  147. }
  148. get adapter(): BodyAdapter<BodyProps, BodyState> {
  149. return {
  150. ...super.adapter,
  151. setVirtualizedData: (virtualizedData, cb) => this.setState({ virtualizedData }, cb),
  152. setCachedExpandBtnShouldInRow: cachedExpandBtnShouldInRow => this.setState({ cachedExpandBtnShouldInRow }),
  153. setCachedExpandRelatedProps: cachedExpandRelatedProps => this.setState({ cachedExpandRelatedProps }),
  154. observeBodyResize: (bodyWrapDOM: HTMLDivElement) => {
  155. const { setBodyHasScrollbar } = this.context;
  156. // Callback when the size of the body dom content changes, notifying Table.jsx whether the bodyHasScrollBar exists
  157. const resizeCallback = () => {
  158. const update = () => {
  159. const { offsetWidth, clientWidth } = bodyWrapDOM;
  160. const bodyHasScrollBar = clientWidth < offsetWidth;
  161. setBodyHasScrollbar(bodyHasScrollBar);
  162. };
  163. const requestAnimationFrame = window.requestAnimationFrame || window.setTimeout;
  164. requestAnimationFrame(update);
  165. };
  166. // Monitor body dom resize
  167. if (bodyWrapDOM) {
  168. if (get(window, 'ResizeObserver')) {
  169. if (this.observer) {
  170. this.observer.unobserve(bodyWrapDOM);
  171. this.observer = null;
  172. }
  173. this.observer = new ResizeObserver(resizeCallback);
  174. this.observer.observe(bodyWrapDOM);
  175. } else {
  176. logger.warn(
  177. 'The current browser does not support ResizeObserver,' +
  178. 'and the table may be misaligned after plugging and unplugging the mouse and keyboard.' +
  179. 'You can try to refresh it.'
  180. );
  181. }
  182. }
  183. },
  184. unobserveBodyResize: () => {
  185. const bodyWrapDOM = this.ref.current;
  186. if (this.observer) {
  187. this.observer.unobserve(bodyWrapDOM);
  188. this.observer = null;
  189. }
  190. }
  191. };
  192. }
  193. componentDidUpdate(prevProps: BodyProps, prevState: BodyState) {
  194. const { virtualized, dataSource, expandedRowKeys, columns, scroll } = this.props;
  195. if (virtualized) {
  196. if (
  197. prevProps.dataSource !== dataSource ||
  198. prevProps.expandedRowKeys !== expandedRowKeys ||
  199. prevProps.columns !== columns
  200. ) {
  201. this.foundation.initVirtualizedData();
  202. }
  203. }
  204. const expandRelatedProps = strings.EXPAND_RELATED_PROPS;
  205. const newExpandRelatedProps = expandRelatedProps.map(key => get(this.props, key, undefined));
  206. if (!isEqual(newExpandRelatedProps, prevState.cachedExpandRelatedProps)) {
  207. this.foundation.initExpandBtnShouldInRow(newExpandRelatedProps);
  208. }
  209. const scrollY = get(scroll, 'y');
  210. const bodyWrapDOM = this.ref.current;
  211. if (scrollY && scrollY !== get(prevProps, 'scroll.y')) {
  212. this.foundation.observeBodyResize(bodyWrapDOM);
  213. }
  214. }
  215. forwardRef = (node: HTMLDivElement) => {
  216. const { forwardedRef } = this.props;
  217. this.ref.current = node;
  218. this.foundation.observeBodyResize(node);
  219. if (typeof forwardedRef === 'function') {
  220. forwardedRef(node);
  221. } else if (forwardedRef && typeof forwardedRef === 'object') {
  222. forwardedRef.current = node;
  223. }
  224. };
  225. itemSize = (index: number) => {
  226. const { virtualized, size: tableSize } = this.props;
  227. const { virtualizedData } = this.state;
  228. const virtualizedItem = get(virtualizedData, index);
  229. const defaultConfig = getDefaultVirtualizedRowConfig(tableSize, virtualizedItem.sectionRow);
  230. const itemSize = get(virtualized, 'itemSize', defaultConfig.height);
  231. let realSize = itemSize;
  232. if (typeof itemSize === 'function') {
  233. realSize = itemSize(index, {
  234. expandedRow: get(virtualizedItem, 'expandedRow', false),
  235. sectionRow: get(virtualizedItem, 'sectionRow', false),
  236. });
  237. }
  238. if (realSize < defaultConfig.minHeight) {
  239. logger.warn(`The computed real \`itemSize\` cannot be less than ${defaultConfig.minHeight}`);
  240. }
  241. return realSize;
  242. };
  243. itemKey = (index: number, data: Array<FlattenData | GroupFlattenData>) => get(data, [index, 'key'], index);
  244. handleRowClick = (rowKey: RowKey<any>, e: React.MouseEvent, expand: boolean) => {
  245. const { handleRowExpanded } = this.context;
  246. handleRowExpanded(!expand, rowKey, e);
  247. };
  248. handleVirtualizedScroll = (props = {}) => {
  249. const onScroll = get(this.props.virtualized, 'onScroll');
  250. if (typeof onScroll === 'function') {
  251. onScroll(props);
  252. }
  253. };
  254. /**
  255. * @param {MouseEvent<HTMLDivElement>} e
  256. */
  257. handleVirtualizedBodyScroll = (e: BodyScrollEvent) => {
  258. const { handleBodyScroll } = this.props;
  259. const newScrollLeft = get(e, 'nativeEvent.target.scrollLeft');
  260. const newScrollTop = get(e, 'nativeEvent.target.scrollTop');
  261. if (newScrollTop === this.state.cache.virtualizedScrollTop) {
  262. this.handleVirtualizedScroll({ horizontalScrolling: true });
  263. }
  264. this.state.cache.virtualizedScrollLeft = newScrollLeft;
  265. this.state.cache.virtualizedScrollTop = newScrollTop;
  266. if (typeof handleBodyScroll === 'function') {
  267. handleBodyScroll(e);
  268. }
  269. };
  270. getVirtualizedRowWidth = () => {
  271. const { getCellWidths } = this.context;
  272. const { columns } = this.props;
  273. const cellWidths = getCellWidths(columns);
  274. const rowWidth = arrayAdd(cellWidths, 0, size(columns));
  275. return rowWidth;
  276. };
  277. renderVirtualizedRow = (options: { index?: number; style?: React.CSSProperties; isScrolling?: boolean }) => {
  278. const { index, style } = options;
  279. const { virtualizedData, cachedExpandBtnShouldInRow } = this.state;
  280. const { flattenedColumns } = this.context;
  281. const virtualizedItem: any = get(virtualizedData, [index], {});
  282. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  283. const { key, parentKeys, expandedRow, sectionRow, ...rest } = virtualizedItem;
  284. const rowWidth = this.getVirtualizedRowWidth();
  285. const expandBtnShouldInRow = cachedExpandBtnShouldInRow;
  286. const props = {
  287. ...this.props,
  288. style: {
  289. ...style,
  290. width: rowWidth,
  291. },
  292. ...rest,
  293. columns: flattenedColumns,
  294. index,
  295. expandBtnShouldInRow,
  296. };
  297. // eslint-disable-next-line no-nested-ternary
  298. return sectionRow ?
  299. this.renderSectionRow(props) :
  300. expandedRow ?
  301. this.renderExpandedRow(props) :
  302. this.renderBaseRow(props);
  303. };
  304. // virtualized List innerElementType
  305. renderTbody = React.forwardRef<HTMLDivElement, any>((props: any = {}, ref: React.MutableRefObject<HTMLDivElement> | ((instance: HTMLDivElement) => void)) => (
  306. <div
  307. {...props}
  308. onScroll={(...args) => {
  309. if (props.onScroll) {
  310. props.onScroll(...args);
  311. }
  312. }}
  313. // eslint-disable-next-line react/no-this-in-sfc,react/destructuring-assignment
  314. className={classnames(props.className, `${this.props.prefixCls}-tbody`)}
  315. style={{ ...props.style }}
  316. ref={ref}
  317. />
  318. ));
  319. // virtualized List outerElementType
  320. renderOuter = React.forwardRef<HTMLDivElement, any>((props: any, ref: React.MutableRefObject<HTMLDivElement> | ((instance: HTMLDivElement) => void)) => {
  321. const { children, ...rest } = props;
  322. // eslint-disable-next-line react/no-this-in-sfc
  323. const { handleWheel, prefixCls, emptySlot, dataSource } = this.props;
  324. // eslint-disable-next-line react/no-this-in-sfc
  325. const tableWidth = this.getVirtualizedRowWidth();
  326. const tableCls = classnames(`${prefixCls}`, `${prefixCls}-fixed`);
  327. return (
  328. <div
  329. {...rest}
  330. ref={ref}
  331. onWheel={(...args) => {
  332. if (handleWheel) {
  333. handleWheel(...args);
  334. }
  335. if (rest.onWheel) {
  336. rest.onWheel(...args);
  337. }
  338. }}
  339. onScroll={(...args) => {
  340. // eslint-disable-next-line react/no-this-in-sfc
  341. this.handleVirtualizedBodyScroll(...args);
  342. if (rest.onScroll) {
  343. rest.onScroll(...args);
  344. }
  345. }}
  346. >
  347. <div style={{ width: tableWidth }} className={tableCls}>
  348. {size(dataSource) === 0 ? emptySlot : children}
  349. </div>
  350. </div>
  351. );
  352. });
  353. onItemsRendered = (props: { overscanStartIndex: number; overscanStopIndex: number; visibleStartIndex: number; visibleStopIndex: number }) => {
  354. if (this.state.cache.virtualizedScrollLeft && this.ref.current) {
  355. this.ref.current.scrollLeft = this.state.cache.virtualizedScrollLeft;
  356. }
  357. };
  358. renderVirtualizedBody = (direction?: Direction) => {
  359. const { scroll, prefixCls, virtualized, anyColumnFixed, columns } = this.props;
  360. const { virtualizedData } = this.state;
  361. const { getCellWidths } = this.context;
  362. const cellWidths = getCellWidths(columns);
  363. if (!size(cellWidths)) {
  364. return null;
  365. }
  366. const rawY = get(scroll, 'y');
  367. const yIsNumber = typeof rawY === 'number';
  368. const y = yIsNumber ? rawY : 600;
  369. if (!yIsNumber) {
  370. logger.warn('You have to specific "scroll.y" which must be a number for table virtualization!');
  371. }
  372. const listStyle = {
  373. width: '100%',
  374. height: y,
  375. overflowX: 'auto',
  376. overflowY: 'auto',
  377. } as const;
  378. const wrapCls = classnames(`${prefixCls}-body`);
  379. return (
  380. <List<Array<FlattenData | GroupFlattenData>>
  381. {...virtualized}
  382. initialScrollOffset={this.state.cache.virtualizedScrollTop}
  383. onScroll={this.handleVirtualizedScroll}
  384. onItemsRendered={this.onItemsRendered}
  385. ref={this.listRef}
  386. className={wrapCls}
  387. outerRef={this.forwardRef}
  388. height={listStyle.height}
  389. width={listStyle.width}
  390. itemData={virtualizedData}
  391. itemSize={this.itemSize}
  392. itemCount={virtualizedData.length}
  393. itemKey={this.itemKey}
  394. innerElementType={this.renderTbody}
  395. outerElementType={this.renderOuter}
  396. style={{ ...listStyle, direction }}
  397. direction={direction}
  398. >
  399. {this.renderVirtualizedRow}
  400. </List>
  401. );
  402. };
  403. /**
  404. * render group title
  405. * @param {*} props
  406. */
  407. renderSectionRow = (props: RenderSectionRowProps = { groupKey: undefined }) => {
  408. const { dataSource, rowKey, group, groupKey, index } = props;
  409. const sectionRowPickKeys = Object.keys(SectionRow.propTypes);
  410. const sectionRowProps: any = pick(props, sectionRowPickKeys);
  411. const { handleRowExpanded } = this.context;
  412. return (
  413. <SectionRow
  414. {...sectionRowProps}
  415. record={{
  416. groupKey,
  417. records: [...group].map(recordKey => getRecord(dataSource, recordKey, rowKey)),
  418. }}
  419. index={index}
  420. onExpand={handleRowExpanded}
  421. data={dataSource}
  422. key={groupKey || index}
  423. />
  424. );
  425. };
  426. renderExpandedRow = (props: RenderExpandedRowProps = { renderExpandIcon: () => null }) => {
  427. const {
  428. style,
  429. components,
  430. renderExpandIcon,
  431. expandedRowRender,
  432. record,
  433. columns,
  434. expanded,
  435. index,
  436. rowKey,
  437. virtualized,
  438. } = props;
  439. let key = getRecordKey(record, rowKey);
  440. if (key == null) {
  441. key = index;
  442. }
  443. const { flattenedColumns, getCellWidths } = this.context;
  444. const cellWidths = getCellWidths(flattenedColumns);
  445. return (
  446. <ExpandedRow
  447. style={style}
  448. components={components}
  449. renderExpandIcon={renderExpandIcon}
  450. expandedRowRender={expandedRowRender}
  451. record={record}
  452. columns={columns}
  453. expanded={expanded}
  454. index={index}
  455. virtualized={virtualized}
  456. key={genExpandedRowKey(key)}
  457. cellWidths={cellWidths}
  458. />
  459. );
  460. };
  461. /**
  462. * render base row
  463. * @param {*} props
  464. * @returns
  465. */
  466. renderBaseRow(props: any = {}) {
  467. const {
  468. rowKey,
  469. columns,
  470. expandedRowKeys,
  471. rowExpandable,
  472. record,
  473. index,
  474. level,
  475. expandBtnShouldInRow, // effect the display of the indent span
  476. selectedRowKeysSet,
  477. disabledRowKeysSet,
  478. expandRowByClick,
  479. } = props;
  480. const baseRowPickKeys = Object.keys(BaseRow.propTypes);
  481. const baseRowProps: Record<string, any> = pick(props, baseRowPickKeys);
  482. let key = getRecordKey(record, rowKey);
  483. if (key == null) {
  484. key = index;
  485. }
  486. const expanded = isExpanded(expandedRowKeys, key);
  487. const expandable = rowExpandable && rowExpandable(record);
  488. const expandableProps: {
  489. level?: number;
  490. expanded?: boolean;
  491. expandableRow?: boolean;
  492. onRowClick?: (...args: any[]) => void;
  493. } = {
  494. level: undefined,
  495. expanded,
  496. };
  497. if (expandable || expandBtnShouldInRow) {
  498. expandableProps.level = level;
  499. expandableProps.expandableRow = expandable;
  500. if (expandRowByClick) {
  501. expandableProps.onRowClick = this.handleRowClick;
  502. }
  503. }
  504. const selectionProps = {
  505. selected: isSelected(selectedRowKeysSet, key),
  506. disabled: isDisabled(disabledRowKeysSet, key),
  507. };
  508. const { getCellWidths } = this.context;
  509. const cellWidths = getCellWidths(columns, null, true);
  510. return (
  511. <BaseRow
  512. {...baseRowProps}
  513. {...expandableProps}
  514. {...selectionProps}
  515. key={key}
  516. rowKey={key}
  517. cellWidths={cellWidths}
  518. />
  519. );
  520. }
  521. /**
  522. * render grouped rows
  523. * @returns {ReactNode[]} renderedRows
  524. */
  525. renderGroupedRows = () => {
  526. const { groups, dataSource: data, rowKey, expandedRowKeys } = this.props;
  527. const { flattenedColumns } = this.context;
  528. const groupsInData = new Map();
  529. const renderedRows: ReactNode[] = [];
  530. if (groups != null && Array.isArray(data) && data.length) {
  531. data.forEach(record => {
  532. const recordKey = getRecordKey(record, rowKey);
  533. groups.forEach((group: Map<string, Record<string, any>[]>, key: number) => {
  534. if (group.has(recordKey)) {
  535. if (!groupsInData.has(key)) {
  536. groupsInData.set(key, new Set([]));
  537. }
  538. groupsInData.get(key).add(recordKey);
  539. return false;
  540. }
  541. return undefined;
  542. });
  543. });
  544. }
  545. let index = -1;
  546. groupsInData.forEach((group, groupKey) => {
  547. // Calculate the expanded state of the group
  548. const expanded = isExpanded(expandedRowKeys, groupKey);
  549. // Render the title of the group
  550. renderedRows.push(
  551. this.renderSectionRow({
  552. ...this.props,
  553. columns: flattenedColumns,
  554. index: ++index,
  555. group,
  556. groupKey,
  557. expanded,
  558. })
  559. );
  560. // Render the grouped content when the group is expanded
  561. if (expanded) {
  562. const dataInGroup: any[] = [];
  563. group.forEach((recordKey: string) => {
  564. const record = getRecord(data, recordKey, rowKey);
  565. if (record != null) {
  566. dataInGroup.push(record);
  567. }
  568. });
  569. /**
  570. * Render the contents of the group row
  571. */
  572. renderedRows.push(this.renderBodyRows(dataInGroup));
  573. }
  574. });
  575. return renderedRows;
  576. };
  577. renderBodyRows(data: Record<string, any>[] = [], level = 0, renderedRows: ReactNode[] = []) {
  578. const {
  579. rowKey,
  580. expandedRowRender,
  581. expandedRowKeys,
  582. childrenRecordName,
  583. rowExpandable,
  584. } = this.props;
  585. const hasExpandedRowRender = typeof expandedRowRender === 'function';
  586. const expandBtnShouldInRow = this.state.cachedExpandBtnShouldInRow;
  587. const { flattenedColumns } = this.context;
  588. each(data, (record, index) => {
  589. let key = getRecordKey(record, rowKey);
  590. if (key == null) {
  591. key = index;
  592. }
  593. const recordChildren = get(record, childrenRecordName);
  594. const recordHasChildren = Boolean(Array.isArray(recordChildren) && recordChildren.length);
  595. renderedRows.push(
  596. this.renderBaseRow({
  597. ...this.props,
  598. columns: flattenedColumns,
  599. expandBtnShouldInRow,
  600. record,
  601. key,
  602. level,
  603. index,
  604. })
  605. );
  606. // render expand row
  607. const expanded = isExpanded(expandedRowKeys, key);
  608. if (hasExpandedRowRender && rowExpandable && rowExpandable(record) && expanded) {
  609. const currentExpandRow = this.renderExpandedRow({
  610. ...this.props,
  611. columns: flattenedColumns,
  612. level,
  613. index,
  614. record,
  615. expanded,
  616. });
  617. /**
  618. * If expandedRowRender returns falsy, this expanded row will not be rendered
  619. * Render an empty div before v1.19.7
  620. */
  621. if (!isNull(currentExpandRow)) {
  622. renderedRows.push(currentExpandRow);
  623. }
  624. }
  625. // render tree data
  626. if (recordHasChildren && expanded) {
  627. const nestedRows = this.renderBodyRows(recordChildren, level + 1);
  628. renderedRows.push(...nestedRows);
  629. }
  630. });
  631. return renderedRows;
  632. }
  633. renderBody = (direction?: Direction) => {
  634. const {
  635. scroll,
  636. prefixCls,
  637. columns,
  638. components,
  639. fixed,
  640. handleWheel,
  641. headerRef,
  642. handleBodyScroll,
  643. anyColumnFixed,
  644. showHeader,
  645. emptySlot,
  646. includeHeader,
  647. dataSource,
  648. onScroll,
  649. groups,
  650. } = this.props;
  651. const x = get(scroll, 'x');
  652. const y = get(scroll, 'y');
  653. const bodyStyle: {
  654. maxHeight?: string | number;
  655. overflow?: string;
  656. WebkitTransform?: string;
  657. } = {};
  658. const tableStyle: {
  659. width?: string | number;
  660. } = {};
  661. const Table = get(components, 'body.outer', 'table');
  662. const BodyWrapper = get(components, 'body.wrapper') || 'tbody';
  663. if (y) {
  664. bodyStyle.maxHeight = y;
  665. }
  666. if (x) {
  667. tableStyle.width = x;
  668. }
  669. if (anyColumnFixed && size(dataSource)) {
  670. // Auto is better than scroll. For example, when there is only scrollY, the scroll axis is not displayed horizontally.
  671. bodyStyle.overflow = 'auto';
  672. // Fix weird webkit render bug
  673. bodyStyle.WebkitTransform = 'translate3d (0, 0, 0)';
  674. }
  675. const colgroup = <ColGroup components={get(components, 'body')} columns={columns} prefixCls={prefixCls} />;
  676. // const tableBody = this.renderBody();
  677. const wrapCls = `${prefixCls}-body`;
  678. const baseTable = (
  679. <div
  680. key="bodyTable"
  681. className={wrapCls}
  682. style={bodyStyle}
  683. ref={this.forwardRef}
  684. onWheel={handleWheel}
  685. onScroll={handleBodyScroll}
  686. >
  687. <Table
  688. style={tableStyle}
  689. className={classnames(prefixCls, {
  690. [`${prefixCls}-fixed`]: anyColumnFixed,
  691. })}
  692. >
  693. {colgroup}
  694. {includeHeader && showHeader ? (
  695. <TableHeader {...this.props} ref={headerRef} components={components} columns={columns} />
  696. ) : null}
  697. <BodyWrapper className={`${prefixCls}-tbody`} onScroll={onScroll}>
  698. {isMap(groups) ? this.renderGroupedRows() : this.renderBodyRows(dataSource)}
  699. </BodyWrapper>
  700. </Table>
  701. {emptySlot}
  702. </div>
  703. );
  704. if (fixed && columns.length) {
  705. return (
  706. <div key="bodyTable" className={`${prefixCls}-body-outer`}>
  707. {baseTable}
  708. </div>
  709. );
  710. }
  711. return baseTable;
  712. };
  713. render() {
  714. const { virtualized } = this.props;
  715. return (
  716. <ConfigContext.Consumer>
  717. {({ direction }: { direction?: Direction }) => (virtualized ? this.renderVirtualizedBody(direction) : this.renderBody(direction))}
  718. </ConfigContext.Consumer>
  719. );
  720. }
  721. }
  722. export default React.forwardRef<HTMLDivElement, Omit<BodyProps, 'forwardedRef'>>(function TableBody(props, ref) {
  723. return <Body {...props} forwardedRef={ref} />;
  724. });
  725. export interface RenderExpandedRowProps {
  726. style?: React.CSSProperties;
  727. components?: TableComponents;
  728. renderExpandIcon: (record?: Record<string, any>, isNested?: boolean) => ReactNode | null;
  729. expandedRowRender?: ExpandedRowRender<Record<string, any>>;
  730. record?: Record<string, any>;
  731. columns?: ColumnProps[];
  732. expanded?: boolean;
  733. index?: number;
  734. rowKey?: RowKey<Record<string, any>>;
  735. virtualized?: Virtualized;
  736. level?: number;
  737. }
  738. export interface RenderSectionRowProps {
  739. dataSource?: Record<string, any>[];
  740. columns?: ColumnProps[];
  741. rowKey?: RowKey<Record<string, any>>;
  742. group?: any;
  743. groupKey: string | number;
  744. index?: number;
  745. expanded?: boolean;
  746. }