index.tsx 29 KB

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