TableHeader.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /* eslint-disable max-len */
  2. import React, { ReactNode } from 'react';
  3. import BaseComponent, { BaseProps } from '../_base/baseComponent';
  4. import PropTypes from 'prop-types';
  5. import { strings, cssClasses } from '@douyinfe/semi-foundation/table/constants';
  6. import { noop, isFunction } from 'lodash';
  7. import TableHeaderRow from './TableHeaderRow';
  8. import { Fixed, TableComponents, OnHeaderRow } from './interface';
  9. function parseHeaderRows(columns: any[]) {
  10. const rows: any[] = [];
  11. // eslint-disable-next-line @typescript-eslint/no-shadow
  12. function fillRowCells(columns: any[], colIndex: number, parents: any[] = [], rowIndex = 0, level = 0) {
  13. // Init rows
  14. rows[rowIndex] = rows[rowIndex] || [];
  15. let currentColIndex = colIndex;
  16. const colSpans = columns.map(column => {
  17. const cell: TableHeaderCell = {
  18. key: column.key,
  19. className: column.className || '',
  20. children: isFunction(column.title) ? column.title() : column.title,
  21. column,
  22. colStart: currentColIndex,
  23. level,
  24. parents,
  25. };
  26. let colSpan = 1;
  27. /**
  28. * Calculate header column merge colSpan
  29. * - If the current cell has children, colSpan = the sum of children rowSpan
  30. * - If the current cell has no children, colSpan = 1
  31. */
  32. const subColumns = column.children;
  33. if (subColumns && subColumns.length > 0) {
  34. colSpan = fillRowCells(subColumns, currentColIndex, [...parents, cell], rowIndex + 1, level + 1).reduce(
  35. (total, count) => total + count,
  36. 0
  37. );
  38. cell.hasSubColumns = true;
  39. }
  40. if ('colSpan' in column) {
  41. ({ colSpan } = column);
  42. }
  43. if ('rowSpan' in column) {
  44. cell.rowSpan = column.rowSpan;
  45. }
  46. if (column.key === strings.DEFAULT_KEY_COLUMN_SCROLLBAR) {
  47. cell['x-type'] = strings.DEFAULT_KEY_COLUMN_SCROLLBAR;
  48. }
  49. cell.colSpan = colSpan;
  50. cell.colEnd = cell.colStart + colSpan - 1;
  51. rows[rowIndex].push(cell);
  52. currentColIndex += colSpan;
  53. return colSpan;
  54. });
  55. return colSpans;
  56. }
  57. // Generate `rows` cell data
  58. fillRowCells(columns, 0);
  59. /**
  60. * Calculate header row merge rowSpan
  61. * - If the current cell has no children, you need to calculate rowSpan, rowSpan = the total number of rows in the header-which row currently belongs to
  62. * - If the current cell has children, there is no need to calculate rowSpan
  63. *
  64. * 计算表头行合并 rowSpan
  65. * - 如果当前cell没有children,则需要计算rowSpan,rowSpan = 表头总行数 - 当前属于第几行
  66. * - 如果当前cell有children,则无需计算rowSpan
  67. */
  68. const rowCount = rows.length;
  69. for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
  70. rows[rowIndex].forEach((cell: TableHeaderCell) => {
  71. if (!('rowSpan' in cell) && !cell.hasSubColumns) {
  72. // eslint-disable-next-line no-param-reassign
  73. cell.rowSpan = rowCount - rowIndex;
  74. }
  75. });
  76. }
  77. return rows;
  78. }
  79. export interface TableHeaderProps extends BaseProps {
  80. columns?: any[];
  81. components?: TableComponents;
  82. fixed?: Fixed;
  83. forwardedRef?: React.MutableRefObject<HTMLDivElement> | ((instance: HTMLDivElement) => void);
  84. onDidUpdate?: (ref: React.MutableRefObject<any>) => void;
  85. onHeaderRow?: OnHeaderRow<any>;
  86. prefixCls?: string;
  87. selectedRowKeysSet: Set<any>;
  88. }
  89. /**
  90. * Render the header of the table header, and control the merging of the columns of the header
  91. */
  92. class TableHeader extends BaseComponent<TableHeaderProps, Record<string, any>> {
  93. static propTypes = {
  94. components: PropTypes.any,
  95. columns: PropTypes.array,
  96. columnManager: PropTypes.object,
  97. prefixCls: PropTypes.string,
  98. onHeaderRow: PropTypes.func,
  99. onDidUpdate: PropTypes.func,
  100. fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
  101. selectedRowKeysSet: PropTypes.instanceOf(Set).isRequired,
  102. };
  103. static defaultProps = {
  104. columns: [] as [],
  105. prefixCls: cssClasses.PREFIX,
  106. onHeaderRow: noop,
  107. onDidUpdate: noop,
  108. components: {
  109. header: {
  110. wrapper: 'thead',
  111. row: 'tr',
  112. cell: 'th',
  113. },
  114. },
  115. };
  116. get adapter() {
  117. return {
  118. ...super.adapter,
  119. };
  120. }
  121. render() {
  122. const { components, columns, prefixCls, fixed, onHeaderRow, forwardedRef, selectedRowKeysSet } = this.props;
  123. const rows = parseHeaderRows(columns);
  124. const HeaderWrapper: any = components.header.wrapper;
  125. return (
  126. <HeaderWrapper className={`${prefixCls}-thead`} ref={forwardedRef}>
  127. {rows.map((row, idx) => (
  128. <TableHeaderRow
  129. prefixCls={prefixCls}
  130. key={idx}
  131. index={idx}
  132. fixed={fixed}
  133. columns={columns}
  134. row={row}
  135. components={components}
  136. onHeaderRow={onHeaderRow}
  137. selectedRowKeysSet={selectedRowKeysSet}
  138. />
  139. ))}
  140. </HeaderWrapper>
  141. );
  142. }
  143. }
  144. export interface TableHeaderCell {
  145. key: string | number;
  146. className: string;
  147. children: ReactNode;
  148. column: any[];
  149. colStart: number;
  150. level: number;
  151. parents: any[];
  152. hasSubColumns?: boolean;
  153. rowSpan?: number;
  154. colSpan?: number;
  155. colEnd?: number;
  156. }
  157. export default React.forwardRef<HTMLDivElement, Omit<TableHeaderProps, 'forwardedRef'>>((props, ref) => <TableHeader {...props} forwardedRef={ref} />);