TableHeader.tsx 6.3 KB

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