TableHeader.tsx 6.2 KB

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