ResizableTable.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /* eslint-disable react-hooks/exhaustive-deps */
  2. import React, { useState, useEffect, useMemo } from 'react';
  3. import { merge, get, find, noop } from 'lodash';
  4. import { addClass, removeClass } from '@douyinfe/semi-foundation/utils/classnames';
  5. import { strings, numbers } from '@douyinfe/semi-foundation/table/constants';
  6. import { assignColumnKeys, findColumn, withResizeWidth } from '@douyinfe/semi-foundation/table/utils';
  7. import Table from './Table';
  8. import { cloneDeep, mergeColumns } from './utils';
  9. import getColumns from './getColumns';
  10. import ResizableHeaderCell from './ResizableHeaderCell';
  11. import type { ResizableProps, TableProps, ColumnProps } from './interface';
  12. const ResizableTable = (props: TableProps = {}, ref: React.MutableRefObject<Table<any>> | ((instance: Table<any>) => void)) => {
  13. const { components: propComponents, columns: propColumns, resizable, ...restProps } = props;
  14. const childrenColumnName = 'children';
  15. const onResize = get(resizable, 'onResize', noop) as ResizableProps<any>['onResize'];
  16. const onResizeStart = get(resizable, 'onResizeStart', noop) as ResizableProps<any>['onResize'];
  17. const onResizeStop = get(resizable, 'onResizeStop', noop) as ResizableProps<any>['onResize'];
  18. /**
  19. * 此处关于 columns 有三个存储
  20. *
  21. * 1. rawColumns 是根据 props.columns 或者 props.children 解析出来的原始 columns
  22. * 2. newColumns 是 rawColumns 的深拷贝,同时根据 props.expandedRowRender、props.hideExpandedColumn 和 props.rowSelection
  23. * 这三个参数加入了【选择列】以及【展开列】
  24. * 3. columns 是当前组件中存储的 state,一般情况下与 newColumns 相等,但是会保存列当前伸缩的宽度
  25. */
  26. /**
  27. * There are three stores for columns here
  28. *
  29. * 1. rawColumns are the original columns parsed according to props.columns or props.children
  30. * 2. newColumns is a deep copy of rawColumns, based on props.expandedRowRender, props.hideExpandedColumn and props.rowSelection
  31. * These three parameters have been added [Select Column] and [Expand Column]
  32. * 3. columns is the state stored in the current component, which is generally equal to newColumns, but it will save the current stretched width of the column
  33. */
  34. const parsedColumns = Array.isArray(propColumns) && propColumns.length ? propColumns : getColumns(props.children);
  35. const rawColumns = assignColumnKeys(cloneDeep(parsedColumns), childrenColumnName);
  36. const newColumns = assignColumnKeys(cloneDeep(parsedColumns), childrenColumnName);
  37. if (
  38. typeof props.expandedRowRender === 'function' &&
  39. !props.hideExpandedColumn &&
  40. !find(rawColumns, item => item.key === strings.DEFAULT_KEY_COLUMN_EXPAND)
  41. ) {
  42. newColumns.unshift({ key: strings.DEFAULT_KEY_COLUMN_EXPAND, width: numbers.DEFAULT_WIDTH_COLUMN_EXPAND });
  43. }
  44. if (props.rowSelection && !get(props.rowSelection, 'hidden') && !find(rawColumns, item => item.key === strings.DEFAULT_KEY_COLUMN_SELECTION)) {
  45. newColumns.unshift({
  46. width: get(props, 'rowSelection.width', numbers.DEFAULT_WIDTH_COLUMN_SELECTION),
  47. key: strings.DEFAULT_KEY_COLUMN_SELECTION,
  48. });
  49. }
  50. const [columns, setColumns] = useState(newColumns);
  51. useEffect(() => {
  52. // If there is a resize value, the width does not use the default value fix#1072
  53. const _newColumns = withResizeWidth(columns, newColumns);
  54. setColumns(mergeColumns(columns, _newColumns));
  55. }, [propColumns, props.expandedRowRender, props.hideExpandedColumn, props.rowSelection]);
  56. const components = useMemo(() => merge(
  57. {
  58. header: {
  59. cell: ResizableHeaderCell,
  60. },
  61. },
  62. propComponents
  63. ), [propComponents]);
  64. const handlerClassName = get(resizable, 'handlerClassName', 'resizing');
  65. const handleResize = (column: ColumnProps) => (e: React.MouseEvent, { size }: { size: { width: number } }) => {
  66. const nextColumns = cloneDeep(columns);
  67. const curColumn: ColumnProps = findColumn(nextColumns, column, childrenColumnName);
  68. let nextColumn = {
  69. ...curColumn,
  70. width: size.width,
  71. };
  72. const customProps = onResize(nextColumn) || {};
  73. nextColumn = {
  74. ...nextColumn,
  75. ...customProps,
  76. };
  77. Object.assign(curColumn, nextColumn);
  78. setColumns(nextColumns);
  79. };
  80. const handleResizeStart = (column: ColumnProps<any>) => (e: React.MouseEvent) => {
  81. const nextColumns = cloneDeep(columns);
  82. const curColumn: ColumnProps = findColumn(nextColumns, column, childrenColumnName);
  83. let nextColumn: ColumnProps = {
  84. ...curColumn,
  85. className: addClass(curColumn.className, handlerClassName),
  86. };
  87. const customProps = onResizeStart(nextColumn) || {};
  88. nextColumn = {
  89. ...nextColumn,
  90. ...customProps,
  91. };
  92. Object.assign(curColumn, nextColumn);
  93. setColumns(nextColumns);
  94. };
  95. const handleResizeStop = (column: ColumnProps) => (e: React.MouseEvent) => {
  96. const nextColumns = cloneDeep(columns);
  97. const curColumn: ColumnProps = findColumn(nextColumns, column, childrenColumnName);
  98. let nextColumn = {
  99. ...curColumn,
  100. className: removeClass(curColumn.className, handlerClassName),
  101. };
  102. const customProps = onResizeStop(nextColumn) || {};
  103. nextColumn = {
  104. ...nextColumn,
  105. ...customProps,
  106. };
  107. Object.assign(curColumn, nextColumn);
  108. setColumns(nextColumns);
  109. };
  110. const resizableRender = (col: ColumnProps, index: number, level = 0, originalHeaderCellProps) => ({
  111. ...col,
  112. onHeaderCell: (column: ColumnProps) => {
  113. return {
  114. ...originalHeaderCellProps,
  115. width: column.width,
  116. onResize: handleResize(column),
  117. onResizeStart: handleResizeStart(column),
  118. onResizeStop: handleResizeStop(column),
  119. };
  120. },
  121. });
  122. const assignResizableRender = (columns: ColumnProps[] = [], level = 0) => (Array.isArray(columns) && columns.length ?
  123. columns.map((col, index) => {
  124. const originalHeaderCellProps = col.onHeaderCell?.(col, index, level) ?? {};
  125. Object.assign(col, resizableRender(col, index, level, originalHeaderCellProps));
  126. const children = col[childrenColumnName];
  127. if (Array.isArray(children) && children.length) {
  128. col[childrenColumnName] = assignResizableRender(children, level + 1);
  129. }
  130. return col;
  131. }) :
  132. []);
  133. const finalColumns = useMemo(() => assignResizableRender(columns), [columns]);
  134. return <Table {...restProps} columns={finalColumns} components={components} ref={ref} />;
  135. };
  136. export default React.forwardRef<Table<any>, TableProps>(ResizableTable);